completed -reset option using callback conn_reset

This commit is contained in:
fduncanh
2022-02-06 12:23:36 -05:00
parent 0a5af62fac
commit d5a9a2433b
11 changed files with 92 additions and 48 deletions

View File

@@ -84,6 +84,7 @@
<p><strong>-avdec</strong> 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.</p>
<p><strong>-as <em>audiosink</em></strong> 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.)</p>
<p><strong>-as 0</strong> (or just <strong>-a</strong>) suppresses playing of streamed audio, but displays streamed video.</p>
<p><strong>-reset n</strong> 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.</p>
<p><strong>-nc</strong> maintains previous UxPlay &lt; 1.45 behavior that does <strong>not close</strong> the video window when the the client sends the “Stop Mirroring” signal. <em>This option is currently 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.</em></p>
<p><strong>-t <em>timeout</em></strong> will cause the server to relaunch (without stopping uxplay) if no connections have been present during the previous <em>timeout</em> 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). <em>This option is currently disabled in macOS, for the same reason that requires the -nc option.</em></p>
<h1 id="troubleshooting">Troubleshooting</h1>
@@ -112,7 +113,7 @@
<p>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 TVs, 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.</p>
<h1 id="changelog">ChangeLog</h1>
<p>1.47 2022-02-05 Added -FPSdata option to display (in the terminal) regular reports sent by the client about video streaming performance.<br />
Internal cleanups of processing of video packets received from the client.</p>
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).</p>
<p>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).</p>
<p>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).</p>
<p>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.</p>

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;
}

View File

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

View File

@@ -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<char> hw_addr, std::string name, unsigned short display[5],
@@ -74,10 +75,11 @@ static bool use_video = true;
static unsigned char compression_type = 0;
static std::string audiosink = "autoaudiosink";
static bool use_audio = true;
static bool previous_no_close_behavior = false;
static bool new_window_closing_behavior = true;
static bool close_window;
static std::string decoder = "decodebin";
static bool show_client_FPS_data = false;
static unsigned int max_ntp_timeouts = NTP_TIMEOUT_LIMIT;
static gboolean connection_callback (gpointer loop){
if (!connections_stopped) {
@@ -231,6 +233,7 @@ static void print_info (char *name) {
printf("-as Choose the GStreamer audiosink; default \"autoaudiosink\"\n");
printf(" choices: pulsesink,alsasink,osssink,oss4sink,osxaudiosink,etc.\n");
printf("-as 0 (or -a) Turn audio off, streamed video only\n");
printf("-reset n Reset after 3n seconds client silence (default %d, 0 = never)\n", NTP_TIMEOUT_LIMIT);
printf("-nc do Not Close video window when client stops mirroring\n");
printf("-FPSdata Show video-streaming performance reports sent by client.\n");
printf("-d Enable debug logging\n");
@@ -271,11 +274,13 @@ static bool get_display_settings (std::string value, unsigned short *w, unsigned
}
static bool get_value (const char *str, unsigned int *n) {
// str must be a positive decimal <= input value *n
// if n > 0 str must be a positive decimal <= input value *n
// if n = 0, str must be a non-negative decimal
if (strlen(str) == 0 || strlen(str) > 10 || str[0] == '-') return false;
char *end;
unsigned long l = strtoul(str, &end, 10);
if (*end || l == 0 || (*n > 0 && l > *n)) return false;
if (*end) return false;
if (*n && (l == 0 || l > *n)) return false;
*n = (unsigned int) l;
return true;
}
@@ -448,14 +453,24 @@ int main (int argc, char *argv[]) {
} else if (arg == "-t") {
if (!option_has_value(i, argc, argv[i], argv[i+1])) exit(1);
server_timeout = 0;
get_value(argv[++i], &server_timeout);
bool valid = get_value(argv[++i], &server_timeout);
if (!valid || server_timeout == 0) {
fprintf(stderr,"invalid \"-t %s\", must have -t n with n > 0\n",argv[i]);
exit(1);
}
} else if (arg == "-nc") {
previous_no_close_behavior = true;
new_window_closing_behavior = false;
} else if (arg == "-avdec") {
decoder.erase();
decoder = "h264parse ! avdec_h264";
} else if (arg == "-FPSdata") {
show_client_FPS_data = true;
} else if (arg == "-reset") {
max_ntp_timeouts = 0;
if (!get_value(argv[++i], &max_ntp_timeouts)) {
fprintf(stderr, "invalid \"-reset %s\"; -reset n must have n >= 0, default n = %d\n", argv[i], NTP_TIMEOUT_LIMIT);
exit(1);
}
} else {
LOGE("unknown option %s, stopping\n",argv[i]);
exit(1);
@@ -469,7 +484,7 @@ int main (int argc, char *argv[]) {
#if __APPLE__
/* force use of -nc option on macOS */
LOGI("macOS detected: use -nc option as workaround for GStreamer problem");
previous_no_close_behavior = true;
new_window_closing_behavior = false;
server_timeout = 0;
#endif
@@ -521,6 +536,7 @@ int main (int argc, char *argv[]) {
reconnect:
counter = 0;
compression_type = 0;
close_window = new_window_closing_behavior;
main_loop();
if (relaunch_server || relaunch_video || reset_loop) {
if(reset_loop) {
@@ -529,7 +545,7 @@ int main (int argc, char *argv[]) {
raop_stop(raop);
}
if (use_audio) audio_renderer_stop();
if (use_video) {
if (use_video && close_window) {
video_renderer_destroy();
video_renderer_init(render_logger, server_name.c_str(), videoflip, decoder.c_str(), videosink.c_str());
video_renderer_start();
@@ -575,8 +591,15 @@ extern "C" void conn_destroy (void *cls) {
}
}
extern "C" void conn_reset (void *cls) {
LOGI("***ERROR lost connection with client");
close_window = false; /* leave "frozen" window open */
raop_stop(raop);
reset_loop = true;
}
extern "C" void conn_teardown(void *cls, bool *teardown_96, bool *teardown_110) {
if (*teardown_110 && !previous_no_close_behavior) {
if (*teardown_110 && close_window) {
reset_loop = true;
}
}
@@ -605,12 +628,6 @@ extern "C" void video_flush (void *cls) {
}
}
extern "C" void video_conn_reset (void *cls) {
if (use_video) {
LOGI("***ERROR Exited raop_rtp_mirror thread because ECONNRESET was received");
}
}
extern "C" void audio_set_volume (void *cls, float volume) {
if (use_audio) {
audio_renderer_set_volume(volume);
@@ -658,10 +675,10 @@ int start_raop_server (std::vector<char> hw_addr, std::string name, unsigned sho
memset(&raop_cbs, 0, sizeof(raop_cbs));
raop_cbs.conn_init = conn_init;
raop_cbs.conn_destroy = conn_destroy;
raop_cbs.conn_reset = conn_reset;
raop_cbs.conn_teardown = conn_teardown;
raop_cbs.audio_process = audio_process;
raop_cbs.video_process = video_process;
raop_cbs.video_conn_reset = video_conn_reset;
raop_cbs.audio_flush = audio_flush;
raop_cbs.video_flush = video_flush;
raop_cbs.audio_set_volume = audio_set_volume;
@@ -685,7 +702,8 @@ int start_raop_server (std::vector<char> hw_addr, std::string name, unsigned sho
if (display[4]) raop_set_plist(raop, "overscanned", (int) display[4]);
if (show_client_FPS_data) raop_set_plist(raop, "clientFPSdata", 1);
raop_set_plist(raop, "max_ntp_timeouts", max_ntp_timeouts);
/* network port selection (ports listed as "0" will be dynamically assigned) */
raop_set_tcp_ports(raop, tcp);
raop_set_udp_ports(raop, udp);