diff --git a/CMakeLists.txt b/CMakeLists.txt index 63313ec..971ba9b 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,10 +14,7 @@ include_directories(${X11_INCLUDE_DIR}) endif (ZOOMFIX) -if (NO_AVAHI_FIX) -else() add_definitions(-DSUPPRESS_AVAHI_COMPAT_WARNING) -endif(NO_AVAHI_FIX) add_subdirectory(lib/llhttp) add_subdirectory(lib/playfair) diff --git a/README.md b/README.md index 0a03e0a..4c2f0ed 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,21 @@ AirPlay services to your iPad, iPhone etc. **-s wxh** (e.g. -s 1920x1080 , which is the default ) sets the display resolution (width and height, in pixels). (This may be a request made to the AirPlay client, and perhaps will not - be the final resolution you get). + be the final resolution you get.) w and h are whole numbers with four + digits or less. + +**-s wxh@r** As above, but also informs the AirPlay client about the screen + refresh rate of the display. Default is r=60 (60 Hz); r is a whole number + with three digits or less. + +**-fps n ** sets a maximum frame rate (in frames per second) for the AirPlay + client to stream video; n must be a whole number with 3 digits or less. + (The client may choose to serve video at any frame rate lower + than this; default is 30 fps.) Values greater than the display + refresh rate are ignored, and replaced by the refresh rate. A setting + below 30 fps might be useful to reduce latency if you are running more than + one instance of uxplay at the same time. + **-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 @@ -104,16 +118,16 @@ Also: image transforms that had been added to RPiPlay have been ported to UxPlay **-r {R|L}** 90 degree Right (clockwise) or Left (counter-clockwise) rotations; these are carried out after any **-f** transforms. -# New features available: (v 1.3 2021-08) +# New features available: (v 1.31 2021-08-09) -1. Updates of the RAOP (AirPlay protocol, not AirPlay 2) collection of codes maintained +1. Updates of the RAOP (AirPlay protocol) collection of codes maintained at https://github.com/FD-/RPiPlay.git so it is current as of 2021-08-01, adding all changes since the original release of UxPlay by antimof. This involved crypto updates, replacement of the included plist library by the system-installed version, and a change over to a library llhttp for http parsing. -2. Added the -s, -p, -m, -r and -f options. +2. Added the -s, -p, -m, -r, -f, and -fps options. 3. If "cmake -DZOOMFIX=ON .." is run before compiling, the mirrored window is now visible to screen-sharing applications such as @@ -131,9 +145,10 @@ The program uxplay terminates when Ctrl-C is typed in the terminal window. 5. In principle, multiple instances of uxplay can be run simultaneously using the **-m** (generate random MAC address) option to give each a different ("local" as opposed to "universal") MAC address. -If the **-p** option is used, they also need separate network port choices. +If the **-p [n]** option is used, they also need separate network port choices. (However, there may be a large latency, and running two instances of uxplay -simultaneously on the same computer may not be very useful.) +simultaneously on the same computer may not be very usefu;l using -fps option +to force streaming framerates below 30fps could be useful.) 6. Without the **-p** [n] option, uxplay makes a random dynamic assignment of network ports. This will not work if most ports are closed by a firewall. @@ -148,14 +163,16 @@ with 4 or less digits. It seems that the width and height may be negotiated with the AirPlay client, so this may not be the actual screen geometry that displays. -8. The title on the GStreamer display window is now is the Airplay server name +8. The title on the GStreamer display window is now is the AirPlay server name (default "UxPlay", but can be changed with option **-n**), rather than the program name "uxplay" (note the difference in capitalization). 9. The avahi_compat "nag" warning on startup is suppressed, by placing "AVAHI_COMPAT_NOWARN=1" into the runtime environment when uxplay starts. -(This uses a call to putenv(), If for any reason you dont want this fix, - run cmake as "cmake -DNO_AVAHI_FIX=ON -DZOOMFIX=ON .. "). +(This uses a call to putenv() in a form that is believed to be safe against +memory leaks, at least in modern Linux; if for any reason you don't want +this fix, comment out the line in CMakeLists.txt that activates it when uxplay +is compiled.) # Disclaimer diff --git a/lib/raop.c b/lib/raop.c index 767d15c..df1302e 100755 --- a/lib/raop.c +++ b/lib/raop.c @@ -52,7 +52,8 @@ struct raop_s { unsigned short display_width; unsigned short display_height; - + unsigned short display_refresh_rate; + unsigned short display_max_fps; }; struct raop_conn_s { @@ -319,10 +320,12 @@ raop_init(int max_clients, raop_callbacks_t *callbacks) { raop->data_lport = 0; raop->mirror_data_lport = 0; - /* initialize display width, height */ + /* initialize display width, height, refresh_rate, max_fps */ raop->display_width = 1920; raop->display_height = 1080; - + raop->display_refresh_rate = 60; + raop->display_max_fps = 30; + return raop; } @@ -354,11 +357,17 @@ raop_set_log_level(raop_t *raop, int level) { logger_set_level(raop->logger, level); } -void raop_set_display_size(raop_t *raop, unsigned short width, unsigned short height){ - assert(raop); +void raop_set_display(raop_t *raop, unsigned short width, unsigned short height, + unsigned short refresh_rate, unsigned short max_fps){ + assert(raop); - if (width) raop->display_width = width; - if (height) raop->display_height = height; + if (width) raop->display_width = width; + if (height) raop->display_height = height; + if (refresh_rate) raop->display_refresh_rate = refresh_rate; + if (max_fps) raop->display_max_fps = max_fps; + if (raop->display_max_fps > raop->display_refresh_rate) { + raop->display_max_fps = raop->display_refresh_rate; + } } void diff --git a/lib/raop.h b/lib/raop.h index 1126bb0..bd02d17 100755 --- a/lib/raop.h +++ b/lib/raop.h @@ -54,7 +54,8 @@ 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 void raop_set_display_size(raop_t *raop, unsigned short width, unsigned short height); +RAOP_API void raop_set_display(raop_t *raop, unsigned short width, unsigned short height, + unsigned short refresh_rate, unsigned short max_fps); RAOP_API void raop_set_port(raop_t *raop, unsigned short port); RAOP_API void raop_set_udp_ports(raop_t *raop, unsigned short port[3]); RAOP_API void raop_set_tcp_ports(raop_t *raop, unsigned short port[2]); diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index b65a26a..ab4396d 100755 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -60,16 +60,16 @@ raop_handler_info(raop_conn_t *conn, plist_t audio_formats_node = plist_new_array(); plist_t audio_format_0_node = plist_new_dict(); plist_t audio_format_0_type_node = plist_new_uint(100); - plist_t audio_format_0_audio_input_formats_node = plist_new_uint(67108860); - plist_t audio_format_0_audio_output_formats_node = plist_new_uint(67108860); + plist_t audio_format_0_audio_input_formats_node = plist_new_uint(0x3fffffc); + plist_t audio_format_0_audio_output_formats_node = plist_new_uint(0x3fffffc); plist_dict_set_item(audio_format_0_node, "type", audio_format_0_type_node); plist_dict_set_item(audio_format_0_node, "audioInputFormats", audio_format_0_audio_input_formats_node); plist_dict_set_item(audio_format_0_node, "audioOutputFormats", audio_format_0_audio_output_formats_node); plist_array_append_item(audio_formats_node, audio_format_0_node); plist_t audio_format_1_node = plist_new_dict(); plist_t audio_format_1_type_node = plist_new_uint(101); - plist_t audio_format_1_audio_input_formats_node = plist_new_uint(67108860); - plist_t audio_format_1_audio_output_formats_node = plist_new_uint(67108860); + plist_t audio_format_1_audio_input_formats_node = plist_new_uint(0x3fffffc); + plist_t audio_format_1_audio_output_formats_node = plist_new_uint(0x3fffffc); plist_dict_set_item(audio_format_1_node, "type", audio_format_1_type_node); 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); @@ -102,20 +102,20 @@ raop_handler_info(raop_conn_t *conn, plist_t audio_latencies_node = plist_new_array(); plist_t audio_latencies_0_node = plist_new_dict(); - plist_t audio_latencies_0_output_latency_micros_node = plist_new_uint(0); + plist_t audio_latencies_0_output_latency_micros_node = plist_new_bool(0); plist_t audio_latencies_0_type_node = plist_new_uint(100); plist_t audio_latencies_0_audio_type_node = plist_new_string("default"); - plist_t audio_latencies_0_input_latency_micros_node = plist_new_uint(0); + plist_t audio_latencies_0_input_latency_micros_node = plist_new_bool(0); plist_dict_set_item(audio_latencies_0_node, "outputLatencyMicros", audio_latencies_0_output_latency_micros_node); plist_dict_set_item(audio_latencies_0_node, "type", audio_latencies_0_type_node); plist_dict_set_item(audio_latencies_0_node, "audioType", audio_latencies_0_audio_type_node); plist_dict_set_item(audio_latencies_0_node, "inputLatencyMicros", audio_latencies_0_input_latency_micros_node); plist_array_append_item(audio_latencies_node, audio_latencies_0_node); plist_t audio_latencies_1_node = plist_new_dict(); - plist_t audio_latencies_1_output_latency_micros_node = plist_new_uint(0); + plist_t audio_latencies_1_output_latency_micros_node = plist_new_bool(0); plist_t audio_latencies_1_type_node = plist_new_uint(101); plist_t audio_latencies_1_audio_type_node = plist_new_string("default"); - plist_t audio_latencies_1_input_latency_micros_node = plist_new_uint(0); + plist_t audio_latencies_1_input_latency_micros_node = plist_new_bool(0); plist_dict_set_item(audio_latencies_1_node, "outputLatencyMicros", audio_latencies_1_output_latency_micros_node); plist_dict_set_item(audio_latencies_1_node, "type", audio_latencies_1_type_node); plist_dict_set_item(audio_latencies_1_node, "audioType", audio_latencies_1_audio_type_node); @@ -139,7 +139,8 @@ raop_handler_info(raop_conn_t *conn, plist_t displays_0_width_pixels_node = plist_new_uint(conn->raop->display_width); plist_t displays_0_height_pixels_node = plist_new_uint(conn->raop->display_height); plist_t displays_0_rotation_node = plist_new_bool(0); - plist_t displays_0_refresh_rate_node = plist_new_real(1.0 / 60.0); + plist_t displays_0_refresh_rate_node = plist_new_uint(conn->raop->display_refresh_rate); + plist_t displays_0_max_fps_node = plist_new_uint(conn->raop->display_max_fps); plist_t displays_0_overscanned_node = plist_new_bool(1); plist_t displays_0_features = plist_new_uint(14); @@ -152,6 +153,7 @@ raop_handler_info(raop_conn_t *conn, plist_dict_set_item(displays_0_node, "heightPixels", displays_0_height_pixels_node); plist_dict_set_item(displays_0_node, "rotation", displays_0_rotation_node); plist_dict_set_item(displays_0_node, "refreshRate", displays_0_refresh_rate_node); + plist_dict_set_item(displays_0_node, "maxFPS", displays_0_max_fps_node); 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); diff --git a/uxplay.cpp b/uxplay.cpp index 7ad738f..a795199 100755 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -33,7 +33,7 @@ #include "renderers/video_renderer.h" #include "renderers/audio_renderer.h" -#define VERSION "1.3" +#define VERSION "1.31" #define DEFAULT_NAME "UxPlay" #define DEFAULT_DEBUG_LOG false @@ -41,7 +41,7 @@ #define HIGHEST_PORT 65535 -static int start_server (std::vector hw_addr, std::string name, unsigned short display_size[2], +static int start_server (std::vector hw_addr, std::string name, unsigned short display[4], unsigned short tcp[2], unsigned short udp[3], videoflip_t videoflip[2], bool use_audio, bool debug_log); @@ -116,7 +116,8 @@ static void print_info (char *name) { printf("Usage: %s [-n name] [-s wxh] [-p [n]]\n", name); printf("Options:\n"); printf("-n name Specify the network name of the AirPlay server\n"); - printf("-s wxh Set display resolution: width w height h default 1920x1080\n"); + printf("-s wxh[@r]Set display resolution [refresh_rate] default 1920x1080@60\n"); + printf("-fps n Set maximum streaming fps, default 30 \n"); printf("-f {H|V|I}Horizontal|Vertical flip, or both=Inversion=rotate 180 deg\n"); printf("-r {R|L} rotate 90 degrees Right (cw) or Left (ccw)\n"); printf("-p n Use fixed UDP+TCP network ports n:n+1:n+2. (n>1023)\n"); @@ -127,9 +128,16 @@ static void print_info (char *name) { printf("-v/-h Displays this help and version information\n"); } -static bool get_display_size (char *str, unsigned short *w, unsigned short *h) { - // assume str = wxh is valid if w and h are positive decimal integers with less than 5 digits. + static bool get_display_settings (char *str, unsigned short *w, unsigned short *h, unsigned short *r) { + // assume str = wxh@r is valid if w and h are positive decimal integers + // with no more than 5 digits, r no more than 3 digits. char *str1 = strchr(str,'x'); + char *str2 = strchr(str1,'@'); + if (str2) { + if (strlen(str2) == 0) return false; + str2[0] = '\0'; str2++; + if (strlen(str2) > 3 || str2[0] == '-') return false; + } if (str1 == NULL) return false; str1[0] = '\0'; str1++; if (str1[0] == '-') return false; // first character of str is never '-' @@ -139,6 +147,18 @@ static bool get_display_size (char *str, unsigned short *w, unsigned short *h) { if (*end || *w == 0) return false; *h = (unsigned short) strtoul(str1, &end, 10); if (*end || *h == 0) return false; + if (!str2) return true;; + *r = (unsigned short) strtoul(str2, &end, 10); + if (*end || *r == 0) return false; + return true; +} + +static bool get_fps (char *str, unsigned short *n) { + if (strlen(str) > 3) return false; + char *end; + unsigned long l; + *n = (unsigned short) (l = strtoul(str, &end, 10)); // first character of str is never '-' + if (*end || !l) return false; return true; } @@ -185,6 +205,14 @@ static bool get_videorotate (char *str, videoflip_t *videoflip) { return true; } +bool option_has_value(int i, int argc, char *argv[]) { + if (i >= argc - 1 || argv[i + 1][0] == '-') { + fprintf(stderr,"invalid \"%s\" had no argument\n",argv[i]); + return false; + } + return true; +} + int main (int argc, char *argv[]) { init_signals(); @@ -193,7 +221,7 @@ int main (int argc, char *argv[]) { bool use_audio = true; bool use_random_hw_addr = false; bool debug_log = DEFAULT_DEBUG_LOG; - unsigned short display_size[2] = {0}, tcp[2] = {0}, udp[3] = {0}; + unsigned short display[4] = {0}, tcp[2] = {0}, udp[3] = {0}; videoflip_t videoflip[2] = { NONE , NONE }; #ifdef SUPPRESS_AVAHI_COMPAT_WARNING @@ -207,40 +235,38 @@ int main (int argc, char *argv[]) { for (int i = 1; i < argc; i++) { std::string arg(argv[i]); if (arg == "-n") { - if (i == argc - 1) continue; + if (!option_has_value(i, argc, argv)) exit(1); server_name = std::string(argv[++i]); } else if (arg == "-s") { - if (i == argc - 1 || argv[i + 1][0] == '-') { - fprintf(stderr,"invalid \"-s\" had no argument\n"); - exit(1); - } + if (!option_has_value(i, argc, argv)) exit(1); std::string value(argv[++i]); - if (!get_display_size(argv[i], &display_size[0], &display_size[1])) { - fprintf(stderr, "invalid \"-s %s\"; default is \"-s 1920x1080\" (< 5 digits)\n", + if (!get_display_settings(argv[i], &display[0], &display[1],&display[2])) { + fprintf(stderr, "invalid \"-s %s\"; default is \"-s 1920x1080\" (up to 4 digits)\n", + value.c_str()); + exit(1); + } + } else if (arg == "-fps") { + if (!option_has_value(i, argc, argv)) exit(1); + std::string value(argv[++i]); + if (!get_fps(argv[i], &display[3])) { + fprintf(stderr, "invalid \"-fps %s\"; default is \"-s 30\" (up to 3 digits)\n", value.c_str()); exit(1); } } else if (arg == "-f") { - if (i == argc - 1 || argv[i + 1][0] == '-') { - fprintf(stderr,"invalid \"-f\" had no argument\n"); - exit(1); - } + if (!option_has_value(i, argc, argv)) exit(1); if (!get_videoflip(argv[++i], &videoflip[0])) { fprintf(stderr,"invalid \"-f %s\" , unknown flip type, choices are H, V, I\n",argv[i]); exit(1); } - } else if (arg == "-r") { - if (i == argc - 1 || argv[i + 1][0] == '-') { - fprintf(stderr,"invalid \"-r\" had no argument\n"); - exit(1); - } + if (!option_has_value(i, argc, argv)) exit(1); if (!get_videorotate(argv[++i], &videoflip[1])) { fprintf(stderr,"invalid \"-r %s\" , unknown rotation type, choices are R, L\n",argv[i]); exit(1); } } else if (arg == "-p") { - if (i == argc - 1 || argv[i + 1][0] == '-') { + if (i >= argc - 1 || argv[i + 1][0] == '-') { tcp[0] = 7100; tcp[1] = 7000; udp[0] = 7011; udp[1] = 6001; udp[2] = 6000; continue; @@ -266,10 +292,11 @@ int main (int argc, char *argv[]) { print_info(argv[0]); exit(0); } else { - LOGI("unknown option %s, skipping\n",argv[i]); + LOGE("unknown option %s, stopping\n",argv[i]); + exit(1); } } - + if (udp[0]) LOGI("using network ports UDP %d %d %d TCP %d %d %d\n", udp[0],udp[1], udp[2], tcp[0], tcp[1], tcp[1] + 1); @@ -284,7 +311,7 @@ int main (int argc, char *argv[]) { mac_address.clear(); relaunch: - if (start_server(server_hw_addr, server_name, display_size, tcp, udp, + if (start_server(server_hw_addr, server_name, display, tcp, udp, videoflip,use_audio, debug_log) != 0) { return 1; } @@ -357,7 +384,7 @@ extern "C" void log_callback (void *cls, int level, const char *msg) { } -int start_server (std::vector hw_addr, std::string name, unsigned short display_size[2], +int start_server (std::vector hw_addr, std::string name, unsigned short display[4], unsigned short tcp[2], unsigned short udp[3], videoflip_t videoflip[2], bool use_audio, bool debug_log) { raop_callbacks_t raop_cbs; @@ -399,8 +426,9 @@ int start_server (std::vector hw_addr, std::string name, unsigned short di if (video_renderer) video_renderer_start(video_renderer); if (audio_renderer) audio_renderer_start(audio_renderer); - /* write desired display pixel width, pixel height to raop (use 0 for default values) */ - raop_set_display_size(raop, display_size[0], display_size[1]); + /* write desired display pixel width, pixel height, refresh_rate, */ + /* and max_fps to raop (use 0 for default values) */ + raop_set_display(raop, display[0], display[1], display[2], display[3]); /* network port selection (ports listed as "0" will be dynamically assigned) */ raop_set_tcp_ports(raop, tcp);