improved the port selection option, added ability to choose videosink

This commit is contained in:
fduncanh
2021-08-21 16:33:31 -04:00
parent c71ef0847a
commit 30f32bd793
8 changed files with 147 additions and 78 deletions

View File

@@ -59,10 +59,9 @@ If uxplay starts, but stalls after "Initialized server socket(s)" appears,
it is probably because a firewall is blocking
access to the server on which it is running. If possible, either turn off the firewall
to see if that is the problem, or get three consecutive network ports,
starting at port n, opened for both tcp and udp, and use "uxplay -p n"
starting at port n, all three in the range 1024-65535, opened for both tcp and udp, and use "uxplay -p n"
(or open UDP 6000, 6001, 6011 TCP 7000,7001,7100 and use "uxplay -p").
Try "uxplay -d " (debug log option) to see what is happening. If you use an
nVidia graphics card, make sure that the gstreamer1.0-vaapi
plugin for Intel graphics is *NOT* installed (de-install it!).
@@ -78,7 +77,10 @@ AirPlay services to your iPad, iPhone etc.
in pixels). (This may be a
request made to the AirPlay client, and perhaps will not
be the final resolution you get.) w and h are whole numbers with four
digits or less.
digits or less. Note that the **height** pixel size is the controlling
one used by the client for determining the streaming format; the width is
dynamically adjusted to the shape of the image (portait or landscape
format, depending on how an iPad is held, for example).
**-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
@@ -92,11 +94,19 @@ AirPlay services to your iPad, iPhone etc.
below 30 fps might be useful to reduce latency if you are running more than
one instance of uxplay at the same time.
**-o** turns on an "overscanned" option for the display window. This
reduces the image resolution by using some of the pixels requested
by option -s wxh (or their default values 1920x1080) by adding an empty
boundary frame of unused pixels (which would be lost in a full-screen
dispaly that overscans, and is not displayed by gstreamer).
Recomnendation: **don't use this option**.
**-p** allows you to select the network ports used by UxPlay (these need
**-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, 7011. -p n (e.g. -p
35000) sets TCP and UDP ports n, n+1, n+2. Ports must be in the range
35000) sets TCP and UDP ports n, n+1, n+2. -p n1,n2,n3 (comman separated
values) sets each port separatey; -p n1,n2 sets ports n1,n2,n2+1. -p tcp n
or -p udp n sets just the TCP or UDP ports. Ports must be in the range
[1024-65535].
If the -p option is not used, the ports are chosen dynamically (randomly),
@@ -118,7 +128,13 @@ 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.31 2021-08-09)
**-vs videosink** chooses the GStreamer videosink, instead of letting
autovideosink pick it for you. For example, xvimagesink, vaapisink, or
fpsdisplaysink (which shows the streaming framerate in fps). Using quotes
"..." might allow some parameters to be included with the videosink name.
# New features available: (v 1.32 2021-08-21)
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,
@@ -127,7 +143,7 @@ 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, -f, and -fps options.
2. Added the -s, -o -p, -m, -r, -f, -fps, and -vs options.
3. If "cmake -DZOOMFIX=ON .." is run before compiling,
the mirrored window is now visible to screen-sharing applications such as
@@ -138,6 +154,16 @@ visible for screen-sharing.) Thanks to David Ventura
https://github.com/DavidVentura/UxPlay for the fix
and also for getting it into gstreamer-1.20.
If uxplay was compiled after
cmake was run without -DZOOMFIX=ON, and your gstreamer version is older that
1.20, you can still manually make the window visible with the X11 utility
xdotool, if it is installed on your system:
```
xdotool selectwindow set_window --name "name"
```
where "name" is your choice of name, and then select the uxplay window with
the mouse.
4. The AirPlay server now terminates correctly when the gstreamer display window is
closed, and is relaunched with the same settings to wait for a new connection.
The program uxplay terminates when Ctrl-C is typed in the terminal window.
@@ -174,6 +200,8 @@ 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.)
10. Allow choice (with -vs option) of the videosink that ends the GStreamer pipline.
# Disclaimer
All the resources in this repository are written using only freely available information from the internet. The code and related resources are meant for educational purposes only. It is the responsibility of the user to make sure all local laws are adhered to.

View File

@@ -44,16 +44,19 @@ struct raop_s {
dnssd_t *dnssd;
/* local network ports */
unsigned short port;
unsigned short timing_lport;
unsigned short control_lport;
unsigned short data_lport;
unsigned short mirror_data_lport;
/* plist display items: width, height, refreshRate, maxFPS, overscanned */
uint16_t display_width;
uint16_t display_height;
uint8_t display_refresh_rate;
uint8_t display_max_fps;
uint8_t display_overscanned;
};
struct raop_conn_s {
@@ -320,12 +323,13 @@ raop_init(int max_clients, raop_callbacks_t *callbacks) {
raop->data_lport = 0;
raop->mirror_data_lport = 0;
/* initialize display width, height, refresh_rate, max_fps */
/* initialize display plist parameters */
raop->display_width = 1920;
raop->display_height = 1080;
raop->display_refresh_rate = 60;
raop->display_max_fps = 30;
raop->display_overscanned = 0;
return raop;
}
@@ -358,20 +362,14 @@ raop_set_log_level(raop_t *raop, int level) {
}
void raop_set_display(raop_t *raop, unsigned short width, unsigned short height,
unsigned short refresh_rate, unsigned short max_fps){
unsigned short refresh_rate, unsigned short max_fps, unsigned short overscanned){
assert(raop);
// these must fit into two 8-bit bytes
if (width) raop->display_width = (uint16_t) width;
if (height) raop->display_height = (uint16_t) height;
// these must fit into a single 8-bit byte
if (refresh_rate > 255) refresh_rate = 255;
if (refresh_rate) raop->display_refresh_rate = (uint8_t) refresh_rate;
if (max_fps > 255) max_fps = 255;
if (max_fps) raop->display_max_fps = (uint8_t) max_fps;
if (refresh_rate && refresh_rate < 256) raop->display_refresh_rate = (uint8_t) refresh_rate;
if (max_fps && max_fps < 256) raop->display_max_fps = (uint8_t) max_fps;
if (overscanned) raop->display_overscanned = 1;
}
void

View File

@@ -55,7 +55,7 @@ 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(raop_t *raop, unsigned short width, unsigned short height,
unsigned short refresh_rate, unsigned short max_fps);
unsigned short refresh_rate, unsigned short max_fps, unsigned short overscanned);
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]);

View File

@@ -141,7 +141,7 @@ raop_handler_info(raop_conn_t *conn,
plist_t displays_0_rotation_node = plist_new_bool(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(0);
plist_t displays_0_overscanned_node = plist_new_bool(conn->raop->display_overscanned);
plist_t displays_0_features = plist_new_uint(14);
plist_dict_set_item(displays_0_node, "uuid", displays_0_uuid_node);

View File

@@ -213,9 +213,7 @@ raop_ntp_init_socket(raop_ntp_t *raop_ntp, int use_ipv6)
/* Set port values */
raop_ntp->timing_lport = tport;
logger_log(raop_ntp->logger, LOGGER_INFO, "raop_ntp timing_lport socket %d UDP port %d",
tsock, tport);
logger_log(raop_ntp->logger, LOGGER_DEBUG, "raop_ntp local timing port socket %d port UDP %d", tsock, tport);
return 0;
sockets_cleanup:
@@ -371,6 +369,7 @@ raop_ntp_start(raop_ntp_t *raop_ntp, unsigned short *timing_lport)
return;
}
if (timing_lport) *timing_lport = raop_ntp->timing_lport;
/* Create the thread and initialize running values */
raop_ntp->running = 1;
raop_ntp->joined = 0;

View File

@@ -251,10 +251,8 @@ raop_rtp_init_sockets(raop_rtp_t *raop_rtp, int use_ipv6, int use_udp)
/* Set port values */
raop_rtp->control_lport = cport;
raop_rtp->data_lport = dport;
logger_log(raop_rtp->logger, LOGGER_INFO, "raop_rtp: control_lport socket %d at port UDP %d",
csock, cport);
logger_log(raop_rtp->logger, LOGGER_INFO, "raop_rtp: data_lport socket %d at port UDP %d",
dsock, dport);
logger_log(raop_rtp->logger, LOGGER_DEBUG, "raop_rtp local control port socket %d port UDP %d", csock, cport);
logger_log(raop_rtp->logger, LOGGER_DEBUG, "raop_rtp local data port socket %d port UDP %d", dsock, dport);
return 0;
sockets_cleanup:

View File

@@ -30,7 +30,6 @@
#include "mirror_buffer.h"
#include "stream.h"
static int raop_rtp_init_mirror_sockets(raop_rtp_mirror_t *raop_rtp_mirror, int use_ipv6);
struct h264codec_s {
unsigned char compatibility;
@@ -433,6 +432,8 @@ raop_rtp_mirror_thread(void *arg)
return 0;
}
static int 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, int use_udp, unsigned short *mirror_data_lport)
{
@@ -526,8 +527,7 @@ raop_rtp_init_mirror_sockets(raop_rtp_mirror_t *raop_rtp_mirror, int use_ipv6)
/* Set port values */
raop_rtp_mirror->mirror_data_lport = dport;
logger_log(raop_rtp_mirror->logger, LOGGER_INFO, "raop_rtp: mirror_data_lport socket %d at port TCP %d",
dsock, dport);
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror local data port socket %d port TCP %d", dsock, dport);
return 0;
sockets_cleanup:

View File

@@ -33,7 +33,7 @@
#include "renderers/video_renderer.h"
#include "renderers/audio_renderer.h"
#define VERSION "1.31"
#define VERSION "1.32"
#define DEFAULT_NAME "UxPlay"
#define DEFAULT_DEBUG_LOG false
@@ -41,9 +41,9 @@
#define HIGHEST_PORT 65535
static int start_server (std::vector<char> hw_addr, std::string name, unsigned short display[4],
static int start_server (std::vector<char> hw_addr, std::string name, unsigned short display[5],
unsigned short tcp[2], unsigned short udp[3], videoflip_t videoflip[2],
bool use_audio, bool debug_log);
bool use_audio, bool debug_log, std::string videosink);
static int stop_server ();
@@ -117,20 +117,25 @@ static void print_info (char *name) {
printf("Options:\n");
printf("-n name Specify the network name of the AirPlay server\n");
printf("-s wxh[@r]Set display resolution [refresh_rate] default 1920x1080[@60]\n");
printf("-o Set mirror \"overscanned\" mode on (not usually needed)\n");
printf("-fps n Set maximum allowed streaming framerate, default 30\n");
printf("-f {H|V|I}Horizontal|Vertical flip, or both=Inversion=rotate 180 deg\n");
printf("-r {R|L} rotate 90 degrees Right (cw) or Left (ccw)\n");
printf("-p n Use fixed UDP+TCP network ports n:n+1:n+2. (n>1023)\n");
printf("-p Use legacy UDP 6000:6001:7011 TCP 7000:7001:7100\n");
printf("-p n Use TCP and UDP ports n,n+1,n+2. range 1024-56535\n");
printf(" use \"-p n1,n2,n3\" to set each port, \"n1,n2\" for n3 = n2+1\n");
printf(" \"-p tcp n\" or \"-p udp n\" sets TCP or UDP ports only\n");
printf("-m use random MAC address (use for concurrent UxPlay's)\n");
printf("-a Turn audio off. video output only\n");
printf("-vs choose the GStreamer videosink; default \"autovideosink\"\n");
printf(" choices: ximagesink,xvimagesink,vaapisink,fpsdisplaysink, etc.\n");
printf("-d Enable debug logging\n");
printf("-v/-h Displays this help and version information\n");
}
bool option_has_value(const int i, const int argc, const char *arg, const char *next_arg) {
bool option_has_value(const int i, const int argc, std::string option, const char *next_arg) {
if (i >= argc - 1 || next_arg[0] == '-') {
fprintf(stderr,"invalid \"%s\" had no argument\n", arg);
LOGE("invalid: \"%s\" had no argument", option.c_str());
return false;
}
return true;
@@ -141,14 +146,14 @@ static bool get_display_settings (std::string value, unsigned short *w, unsigned
// with no more than 4 digits, r < 256 (stored in one byte).
char *end;
std::size_t pos = value.find_first_of("x");
if (!pos) return false;
if (pos == std::string::npos) return false;
std::string str1 = value.substr(pos+1);
value.erase(pos);
if (value.length() == 0 || value.length() > 4 || value[0] == '-') return false;
*w = (unsigned short) strtoul(value.c_str(), &end, 10);
if (*end || *w == 0) return false;
pos = str1.find_first_of("@");
if(pos) {
if(pos != std::string::npos) {
std::string str2 = str1.substr(pos+1);
if (str2.length() == 0 || str2.length() > 3 || str2[0] == '-') return false;
*r = (unsigned short) strtoul(str2.c_str(), &end, 10);
@@ -170,15 +175,37 @@ static bool get_fps (const char *str, unsigned short *n) {
return true;
}
static bool get_lowest_port (const char *str, unsigned short *n) {
//str must be a positive decimal integer in allowed range, 5 digits max.
static bool get_ports (int nports, std::string option, const char * value, unsigned short * const port) {
/*valid entries are comma-separated values port_1,port_2,...,port_r, 0 < r <= nports */
/*where ports are distinct, and are in the allowed range. */
/*missing values are consecutive to last given value (at least one value needed). */
char *end;
unsigned long l;
if (strlen(str) == 0 || strlen(str) > 5 || str[0] == '-') return false;
*n = (unsigned short) (l = strtoul(str, &end, 10));
if (*end) return false;
if (l < LOWEST_ALLOWED_PORT || l > HIGHEST_PORT - 2 ) return false;
return true;
std::size_t pos;
std::string val(value), str;
for (int i = 0; i <= nports ; i++) {
if(i == nports) break;
pos = val.find_first_of(',');
str = val.substr(0,pos);
if(str.length() == 0 || str.length() > 5 || str[0] == '-') break;
l = strtoul(str.c_str(), &end, 10);
if (*end || l < LOWEST_ALLOWED_PORT || l > HIGHEST_PORT) break;
*(port + i) = (unsigned short) l;
for (int j = 0; j < i ; j++) {
if( *(port + j) == *(port + i)) break;
}
if(pos == std::string::npos) {
if (nports + *(port + i) > i + 1 + HIGHEST_PORT) break;
for (int j = i + 1; j < nports; j++) {
*(port + j) = *(port + j - 1) + 1;
}
return true;
}
val.erase(0, pos+1);
}
LOGE("invalid \"%s %s\", all %d ports must be in range [%d,%d]",
option.c_str(), value, nports, LOWEST_ALLOWED_PORT, HIGHEST_PORT);
return false;
}
static bool get_videoflip (const char *str, videoflip_t *videoflip) {
@@ -222,8 +249,9 @@ 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[4] = {0}, tcp[2] = {0}, udp[3] = {0};
unsigned short display[5] = {0}, tcp[2] = {0}, udp[3] = {0};
videoflip_t videoflip[2] = { NONE , NONE };
std::string videosink = "autovideosink";
#ifdef SUPPRESS_AVAHI_COMPAT_WARNING
// suppress avahi_compat nag message. avahi emits a "nag" warning (once)
@@ -236,7 +264,7 @@ int main (int argc, char *argv[]) {
for (int i = 1; i < argc; i++) {
std::string arg(argv[i]);
if (arg == "-n") {
if (!option_has_value(i, argc, argv[i], argv[i+1])) exit(1);
if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
server_name = std::string(argv[++i]);
} else if (arg == "-s") {
if (!option_has_value(i, argc, argv[i], argv[i+1])) exit(1);
@@ -247,19 +275,21 @@ int main (int argc, char *argv[]) {
exit(1);
}
} else if (arg == "-fps") {
if (!option_has_value(i, argc, argv[i], argv[i+1])) exit(1);
if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
if (!get_fps(argv[++i], &display[3])) {
fprintf(stderr, "invalid \"-fps %s\"; -fps n : max n=255, default n=30\n", argv[i]);
exit(1);
}
} else if (arg == "-o") {
display[4] = 1;
} else if (arg == "-f") {
if (!option_has_value(i, argc, argv[i], argv[i+1])) exit(1);
if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
if (!get_videoflip(argv[++i], &videoflip[0])) {
fprintf(stderr,"invalid \"-f %s\" , unknown flip type, choices are H, V, I\n",argv[i]);
exit(1);
}
} else if (arg == "-r") {
if (!option_has_value(i, argc, argv[i], argv[i+1])) exit(1);
if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
if (!get_videorotate(argv[++i], &videoflip[1])) {
fprintf(stderr,"invalid \"-r %s\" , unknown rotation type, choices are R, L\n",argv[i]);
exit(1);
@@ -270,16 +300,18 @@ int main (int argc, char *argv[]) {
udp[0] = 7011; udp[1] = 6001; udp[2] = 6000;
continue;
}
unsigned short n;
if(get_lowest_port(argv[++i], &n)) {
for (int i = 0; i < 3; i++) {
udp[i] = n++;
if (i < 2) tcp[i] = udp[i];
}
std::string value(argv[++i]);
if (value == "tcp") {
arg.append(" tcp");
if(!get_ports(3, arg, argv[++i], tcp)) exit(1);
} else if (value == "udp") {
arg.append( " udp");
if(!get_ports(3, arg, argv[++i], udp)) exit(1);
} else {
fprintf(stderr, "Error: \"-p %s\" is invalid (%d-%d is allowed)\n",
argv[i], LOWEST_ALLOWED_PORT, HIGHEST_PORT);
exit(1);
if(!get_ports(3, arg, argv[i], tcp)) exit(1);
for (int j = 1; j < 3; j++) {
udp[j] = tcp[j];
}
}
} else if (arg == "-m") {
use_random_hw_addr = true;
@@ -290,6 +322,10 @@ int main (int argc, char *argv[]) {
} else if (arg == "-h" || arg == "-v") {
print_info(argv[0]);
exit(0);
} else if (arg == "-vs") {
if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
videosink.erase();
videosink.append(argv[++i]);
} else {
LOGE("unknown option %s, stopping\n",argv[i]);
exit(1);
@@ -297,7 +333,7 @@ int main (int argc, char *argv[]) {
}
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);
udp[0],udp[1], udp[2], tcp[0], tcp[1], tcp[2]);
std::string mac_address;
if (!use_random_hw_addr) mac_address = find_mac();
@@ -311,7 +347,7 @@ int main (int argc, char *argv[]) {
relaunch:
if (start_server(server_hw_addr, server_name, display, tcp, udp,
videoflip,use_audio, debug_log) != 0) {
videoflip,use_audio, debug_log, videosink)) {
return 1;
}
running = true;
@@ -383,9 +419,9 @@ extern "C" void log_callback (void *cls, int level, const char *msg) {
}
int start_server (std::vector<char> hw_addr, std::string name, unsigned short display[4],
int start_server (std::vector<char> hw_addr, std::string name, unsigned short display[5],
unsigned short tcp[2], unsigned short udp[3], videoflip_t videoflip[2],
bool use_audio, bool debug_log) {
bool use_audio, bool debug_log, std::string videosink) {
raop_callbacks_t raop_cbs;
memset(&raop_cbs, 0, sizeof(raop_cbs));
raop_cbs.conn_init = conn_init;
@@ -402,6 +438,14 @@ int start_server (std::vector<char> hw_addr, std::string name, unsigned short di
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 */
raop_set_display(raop, display[0], display[1], display[2], display[3], display[4]);
/* network port selection (ports listed as "0" will be dynamically assigned) */
raop_set_tcp_ports(raop, tcp);
raop_set_udp_ports(raop, udp);
raop_set_log_callback(raop, log_callback, NULL);
raop_set_log_level(raop, debug_log ? RAOP_LOG_DEBUG : LOGGER_INFO);
@@ -409,8 +453,9 @@ int start_server (std::vector<char> hw_addr, std::string name, unsigned short di
logger_set_callback(render_logger, log_callback, NULL);
logger_set_level(render_logger, debug_log ? LOGGER_DEBUG : LOGGER_INFO);
if ((video_renderer = video_renderer_init(render_logger, name.c_str(), videoflip)) == NULL) {
if ((video_renderer = video_renderer_init(render_logger, name.c_str(), videoflip, videosink.c_str())) == NULL) {
LOGE("Could not init video renderer");
stop_server();
return -1;
}
@@ -419,20 +464,13 @@ int start_server (std::vector<char> hw_addr, std::string name, unsigned short di
} else if ((audio_renderer = audio_renderer_init(render_logger, video_renderer)) ==
NULL) {
LOGE("Could not init audio renderer");
stop_server();
return -1;
}
if (video_renderer) video_renderer_start(video_renderer);
if (audio_renderer) audio_renderer_start(audio_renderer);
/* 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);
raop_set_udp_ports(raop, udp);
unsigned short port = raop_get_port(raop);
raop_start(raop, &port);
raop_set_port(raop, port);
@@ -441,22 +479,30 @@ int start_server (std::vector<char> hw_addr, std::string name, unsigned short di
dnssd = dnssd_init(name.c_str(), strlen(name.c_str()), hw_addr.data(), hw_addr.size(), &error);
if (error) {
LOGE("Could not initialize dnssd library!");
stop_server();
return -2;
}
raop_set_dnssd(raop, dnssd);
dnssd_register_raop(dnssd, port);
dnssd_register_airplay(dnssd, port + 1);
if (tcp[2]) {
port = tcp[2];
} else if (port < 56535) {
port++;
} else {
port--;
}
dnssd_register_airplay(dnssd, port);
return 0;
}
int stop_server () {
raop_destroy(raop);
dnssd_unregister_raop(dnssd);
dnssd_unregister_airplay(dnssd);
if (raop) raop_destroy(raop);
if (dnssd) dnssd_unregister_raop(dnssd);
if (dnssd) dnssd_unregister_airplay(dnssd);
if (audio_renderer) audio_renderer_destroy(audio_renderer);
video_renderer_destroy(video_renderer);
if (video_renderer) video_renderer_destroy(video_renderer);
return 0;
}