version now 2.35; updated to build on MacOs. Now uses a Glib MainLoop.

This commit is contained in:
fduncanh
2021-09-12 13:24:09 -04:00
parent 6d2471f8c1
commit 201aa2c805
7 changed files with 337 additions and 140 deletions

View File

@@ -1,28 +1,44 @@
cmake_minimum_required(VERSION 3.4.1) cmake_minimum_required( VERSION 3.4.1 )
project(uxplay) project( uxplay )
set (CMAKE_CXX_STANDARD 11) set ( CMAKE_CXX_STANDARD 11 )
if ( ZOOMFIX )
add_definitions( -DX_DISPLAY_FIX )
find_package( X11 REQUIRED )
link_libraries( ${X11_LIBRARIES} )
include_directories( ${X11_INCLUDE_DIR} )
endif ( ZOOMFIX )
if (ZOOMFIX) if( UNIX AND NOT APPLE )
add_definitions(-DX_DISPLAY_FIX) add_definitions( -DSUPPRESS_AVAHI_COMPAT_WARNING )
find_package(X11 REQUIRED) endif()
link_libraries(${X11_LIBRARIES})
include_directories(${X11_INCLUDE_DIR})
# link_directories(${X11_LIBRARIES})
endif (ZOOMFIX)
add_subdirectory( lib/llhttp )
add_subdirectory( lib/playfair )
add_subdirectory( lib )
add_subdirectory( renderers )
add_definitions(-DSUPPRESS_AVAHI_COMPAT_WARNING) add_executable( uxplay uxplay.cpp )
add_subdirectory(lib/llhttp) if( UNIX AND NOT APPLE )
add_subdirectory(lib/playfair) include_directories( uxplay ${GST_INCLUDE_DIRS} )
add_subdirectory(lib) else()
add_subdirectory(renderers) include_directories( uxplay
/Library/FrameWorks/GStreamer.framework/Headers/
/usr/local/include
/usr/local/include/glib-2.0
/usr/local/lib/glib-2.0/include
/opt/local/include
/opt/local/include/glib-2.0
/opt/local/lib/glib-2.0/include
)
endif()
add_executable( uxplay uxplay.cpp) target_link_libraries( uxplay
target_link_libraries ( uxplay renderers airplay ${X11_LIBRARIES}) renderers
airplay
install(TARGETS uxplay RUNTIME DESTINATION bin) )
install( TARGETS uxplay RUNTIME DESTINATION bin )

120
README.md
View File

@@ -1,13 +1,13 @@
This project is an early stage prototype of unix AirPlay server. This project is a unix AirPlay server which now also works on MacOs.
Work is based on https://github.com/FD-/RPiPlay. The work is based on https://github.com/FD-/RPiPlay.
Tested on Ubuntu 19.10 desktop. Tested on Ubuntu 19.10 desktop.
5G Wifi connection is the must. Tested on MacOS 10.15
Features: Features:
1. Based on Gstreamer. 1. Based on Gstreamer.
2. Video and audio are supported out of the box. 2. Video and audio are supported out of the box.
3. Gstreamer decoding is plugin agnostic. Uses accelerated decoders if 3. Gstreamer decoding is plugin agnostic. Uses accelerated decoders if
available. VAAPI is preferable. (but don't use VAAPI with nVidia) available. VAAPI is preferable, (but don't use VAAPI with nVidia)
4. Automatic screen orientation. 4. Automatic screen orientation.
Getting it: (after sudo apt-get-install git cmake): Getting it: (after sudo apt-get-install git cmake):
@@ -18,18 +18,16 @@ This is a pull request on the
original site https://github.com/antimof/UxPlay.git ; it may or may not ever original site https://github.com/antimof/UxPlay.git ; it may or may not ever
get committed into the codebase on the original antimof site, as the antimof get committed into the codebase on the original antimof site, as the antimof
project may no longer be active. project may no longer be active.
If it has been committed, replace "FDH2" by "antimof" in the above. If the pull request ever gets committed, replace "FDH2" by "antimof" in the above.
**Building this version** (Instructions for Ubuntu; adapt these for other Linuxes, and MacOs, see below).
**Building this version** (Instructions for Ubuntu; adapt these for other
Linuxes).
In a terminal window, change directories to the UxPlay directory of the In a terminal window, change directories to the UxPlay directory of the
downloaded source code, then do downloaded source code, then do
1. sudo apt-get install libssl-dev libplist-dev libavahi-compat-libdnssd-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-libav gstreamer1.0-plugins-bad 1. sudo apt-get install libssl-dev libplist-dev libavahi-compat-libdnssd-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-libav gstreamer1.0-plugins-bad
2. sudo apt-get install gstreamer1.0-vaapi (For Intel graphics, but not nVidia graphics) 2. sudo apt-get install gstreamer1.0-vaapi (For Intel graphics, but not nVidia graphics)
3. sudo apt-get install libx11-dev (for the X_display name fix for screen-sharing with e.g., ZOOM) 3. sudo apt-get install libx11-dev (for the "ZOOMFIX" X11_display name fix for screen-sharing with e.g., ZOOM)
4. mkdir build 4. mkdir build
5. cd build 5. cd build
6. cmake .. (or "cmake -DZOOMFIX=ON .." to get a screen-sharing fix to 6. cmake .. (or "cmake -DZOOMFIX=ON .." to get a screen-sharing fix to
@@ -53,6 +51,75 @@ avahi-compat-mDNSResponder-devel (+ libX11-devel for ZOOMFIX). The required
GStreamer packages are: GStreamer packages are:
gstreamer-devel gstreamer-plugins-base-devel gstreamer-plugins-libav gstreamer-plugins-bad (+ gstreamer-plugins-vaapi for Intel graphics). gstreamer-devel gstreamer-plugins-base-devel gstreamer-plugins-libav gstreamer-plugins-bad (+ gstreamer-plugins-vaapi for Intel graphics).
**MacOs** (Currently only for Intel X86_64 Macs)
These instructions asssume that the Xcode command-line developer tools are installed (if Xcode is installed, open the Terminal, type "sudo xcode-select --install" and accept the conditions).
It is also assumed that CMake >= 2.13 is installed:
this can be done with package managers [MacPorts](http://www.macports.org),
[Fink](http://finkproject.org) or [Brew](http://brew.sh), or by a download from
[https://cmake.org/download/](https://cmake.org/download/).
Start by downloading the latest MacOs release of GStreamer-1.0
from [https://gstreamer.freedesktop.org/download/](https://gstreamer.freedesktop.org/download/).
Install both the MacOs runtime and development installer packages. Assuming that the latest release is 1.18.4 they are
```
gstreamer-1.0-1.18.4-x86_64.pkg
gstreamer-1.0-devel-1.18.4-x86_64.pkg
```
Click on them to install (they install to
/Library/FrameWorks/GStreamer.framework).
It is recommended you use GStreamer.framework rather than install Gstreamer with Brew or MacPorts (see later).
Next install OpenSSL-1.1.1 and libplist:
MacPorts: "sudo port install openssl liblist-dev "; Brew: "brew install openssl libplist".
Since the static forms of these libraries are used in the MacOs build, the OpenSSL and libplist packages can be uninstalled after building uxplay
(so you could just install MacPorts or Brew before building uxplay, and uninstall it afterwards).
Unfortunately, Fink's openssl package currently doesn't supply the static (libcrypto.a) form of the needed library libcrypto, and it
does not supply a recent libplist.
If you have have the standard GNU-Linux toolset (autoconf, automake, libtool, etc.) installed,
you can also download and compile the source code for these libraries from
[https://www.openssl.org/source/](https://www.openssl.org/source/),
[https://github.com/libimobiledevice/libplist](https://github.com/libimobiledevice/libplist).
Compile the downloaded
openssl-1.1.1 by opening a terminal in your Downloads directory, and unpacking the source distribution openssl-1.1.1x.tar.gz (where "x" is a "patch" label,
currently given by "x" = "l"):
("tar -xvzf openssl-1.1.1x.tar.gz ; cd openssl-1.1.1x"). Then install with
"./config; make ; sudo make install_dev" and clean up after building uxplay with "sudo make uninstall" in the same directory.
Similarly, for libplist, download the source as a zipfile from github as
[libplist-master.zip](https://github.com/libimobiledevice/libplist/archive/refs/heads/master.zip), then
unpack ("unzip libplist-master.zip ; cd libplist-master"), compile
("./autogen.sh ; make ; sudo make install)" and clean up after uxplay is built with "sudo uninstall" in the same directory.
Finally, build and install uxplay (without ZOOMFIX):
"cd UxPlay; mkdir build ; cd build ; cmake .. ; make ; sudo make install ".
The MacOs build uses OpenGL, not X11, to create the mirror display window. This has some "quirks":
the window title is "OpenGL renderer" instead of the Airplay server name, but it is visible to
screen-sharing apps (e.g., Zoom). The option -t _timeout_
cannot be used because if the GStreamer pipeline is destroyed while the OpenGL window is still open,
and uxplay is left running, a segfault occurs.
Also, the resolution settings "-s wxh" do not affect
the (small) initial mirror window size, but the window can be expanded using the mouse.
**Other ways (Brew, MacPorts) to install GStreamer on MacOs (not recommended):**
First make sure that pkgconfig is installed (Brew: "brew install pkgconfig" ; MacPorts: "sudo port install pkgconfig" ).
(a) with Brew: "brew gst-plugins-good gst-plugins-bad gst-libav". This appears to be functionally equivalent
to using GStreamer.framework, but causes a large number of extra packages to be installed by Brew as dependencies.
(b) with MacPorts: "sudo port install gstreamer1-gst-plugins-good gstreamer1-gst-plugins-bad gstreamer1-gst-libav".
The MacPorts GStreamer is built to use X11, so must be run from an XQuartz terminal, can use ZOOMFIX, and needs
option "-vs ximagesink". On an older unibody MacBook Pro, the default setting wxh = 1920x1080 was too large for
the non-retina display, but 800x600 worked; However, the Gstreamer pipeline is fragile against attempts to change
the X11 window size, or to rotations that switch a connected iPad client between portrait and landscape mode while uxplay is running.
Using the MacPorts X11 GStreamer is only viable if the image size is left unchanged from the initial "-s wxh" setting.
# **Troubleshooting:** # **Troubleshooting:**
If uxplay starts, but stalls after "Initialized server socket(s)" appears, If uxplay starts, but stalls after "Initialized server socket(s)" appears,
@@ -77,7 +144,7 @@ has chosen for you. Maybe an unusual videosink was chosen. Fix: use the -vs op
Options: Options:
**-n server_name **; server_name will be the name that appears offering **-n server_name **; server_name will be the name that appears offering
AirPlay services to your iPad, iPhone etc. AirPlay services to your iPad, iPhone etc.
**NEW**: this will also now be the name shown above the mirror display window, **NEW**: this will also now be the name shown above the mirror display (X11) window,
**-s wxh** (e.g. -s 1920x1080 , which is the default ) sets the display resolution (width and height, **-s wxh** (e.g. -s 1920x1080 , which is the default ) sets the display resolution (width and height,
in pixels). (This may be a in pixels). (This may be a
@@ -126,6 +193,7 @@ which will not work if a firewall is running.
number of the computer's network card. (Different server_name, MAC number of the computer's network card. (Different server_name, MAC
addresses, and network ports are needed for each running uxplay if you addresses, and network ports are needed for each running uxplay if you
attempt to run two instances of uxplay on the same computer.) attempt to run two instances of uxplay on the same computer.)
On MacOs, random MAC addresses are always used.
**-a** disable audio, leaving only the video playing. **-a** disable audio, leaving only the video playing.
@@ -138,20 +206,33 @@ 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) **-r {R|L}** 90 degree Right (clockwise) or Left (counter-clockwise)
rotations; these are carried out after any **-f** transforms. rotations; these are carried out after any **-f** transforms.
**-vs videosink** chooses the GStreamer videosink, instead of letting **-vs _videosink_** chooses the GStreamer videosink, instead of letting
autovideosink pick it for you. For example, xvimagesink, vaapisink, or autovideosink pick it for you. For example, xvimagesink, vaapisink, or
fpsdisplaysink (which shows the streaming framerate in fps). Using quotes fpsdisplaysink (which shows the streaming framerate in fps). Using quotes
"..." might allow some parameters to be included with the videosink name. "..." might allow some parameters to be included with the videosink name.
(Some choices of videosink might not work on your system.) (Some choices of videosink might not work on your system.)
** -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 because an idle Bonjour
registration eventually becomes unavailable for new connections.) This option should not be
used if the display window is an OpenGL window (e.g., on MacOS without X11), as an OpenGL window created
by GStreamer does not terminate correctly (it causes a segfault)
if it is still open when the GStreamer pipeline is closed.
# ChangeLog # ChangeLog
1.341 2021-09-04 fixed: render_logger was not being destroyed by stop_server() 1.35 2021-09-10 now uses a GLib MainLoop, and builds on MacOS (tested on Intel Mac, 10.15 ).
New option -t _timeout_ for relauching server if no connections were active in
previous _timeout_ seconds (to renew Bonjour registration).
1.341 2021-09-04 fixed: render logger was not being destroyed by stop_server()
1.34 2021-08-27 Fixed "ZOOMFIX": the X11 window name fix was only being made the 1.34 2021-08-27 Fixed "ZOOMFIX": the X11 window name fix was only being made the
first time the GStreamer window was created by uxplay, and first time the GStreamer window was created by uxplay, and
not if the server was relaunched after the GStreamer window not if the server was relaunched after the GStreamer window
was closed, with uxplay still running. Corrected in v. 1.34 was closed, with uxplay still running. Corrected in v. 1.34
# New features available: (v 1.32 2021-08-20) # New features available: (v 1.35 2021-09-10)
1. Updates of the RAOP (AirPlay protocol) 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, at https://github.com/FD-/RPiPlay.git so it is current as of 2021-08-01,
@@ -160,7 +241,7 @@ This involved crypto updates, replacement
of the included plist library by the system-installed version, and a change of the included plist library by the system-installed version, and a change
over to a library llhttp for http parsing. over to a library llhttp for http parsing.
2. Added the -s, -o -p, -m, -r, -f, -fps, and -vs options. 2. Added the -s, -o -p, -m, -r, -f, -fps -vs and -t options.
3. If "cmake -DZOOMFIX=ON .." is run before compiling, 3. If "cmake -DZOOMFIX=ON .." is run before compiling,
the mirrored window is now visible to screen-sharing applications such as the mirrored window is now visible to screen-sharing applications such as
@@ -205,18 +286,17 @@ 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 with the AirPlay client, so this may not be the actual screen geometry that
displays. 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 (default "UxPlay", but can be changed with option **-n**), rather than the program
name "uxplay" (note the difference in capitalization). name "uxplay" (note the difference in capitalization). (This works for X11 windows created
by gstreamer videosinks ximagesink, xvimagesink, but not OpenGL windows created by glimagesink.)
9. The avahi_compat "nag" warning on startup is suppressed, by placing 9. The avahi_compat "nag" warning on startup is suppressed, by placing
"AVAHI_COMPAT_NOWARN=1" into the runtime environment when uxplay starts. "AVAHI_COMPAT_NOWARN=1" into the runtime environment when uxplay starts.
(This uses a call to putenv() in a form that is believed to be safe against (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 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 this fix, comment out the line in CMakeLists.txt that activates it when uxplay
is compiled.) is compiled.) On MacOS, Avahi is not used.
10. Allow choice (with -vs option) of the videosink that ends the GStreamer pipline.
# Disclaimer # Disclaimer

View File

@@ -3,6 +3,15 @@ include_directories( playfair llhttp )
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -march=native -DSTANDALONE -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DTARGET_POSIX -D_LINUX -fPIC -DPIC -D_REENTRANT -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -U_FORTIFY_SOURCE -Wall -g") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -march=native -DSTANDALONE -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DTARGET_POSIX -D_LINUX -fPIC -DPIC -D_REENTRANT -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -U_FORTIFY_SOURCE -Wall -g")
if( APPLE )
set( ENV{PKG_CONFIG_PATH} "/usr/local/lib/pkgconfig" ) # standard location, and Brew
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/opt/local/lib/pkgconfig/" ) # MacPorts
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/opt/openssl@1.1/lib/pkgconfig" ) # Brew openssl
message( "PKG_CONFIG_PATH (Apple, lib) = " $ENV{PKG_CONFIG_PATH} )
find_program( PKG_CONFIG_EXECUTABLE pkg-config PATHS /Library/FrameWorks/GStreamer.framework/Commands )
message( "PKG_CONFIG_EXECUTABLE " ${PKG_CONFIG_EXECUTABLE} )
endif()
aux_source_directory(. play_src) aux_source_directory(. play_src)
set(DIR_SRCS ${play_src}) set(DIR_SRCS ${play_src})
@@ -11,17 +20,46 @@ add_library( airplay
${DIR_SRCS} ${DIR_SRCS}
) )
find_library( LIBPLIST NAMES plist plist-2.0 ) find_package(PkgConfig REQUIRED)
if( UNIX AND NOT APPLE )
find_library( LIBPLIST NAMES plist plist-2.0 )
elseif( APPLE )
pkg_check_modules( PLIST REQUIRED libplist-2.0 )
find_library( LIBPLIST libplist-2.0.a REQUIRED )
message( "LIBPLIST" ${LIBPLIST} )
target_include_directories( airplay PRIVATE
/usr/local/include # standard and MacPorts
/opt/local/include # MacPorts
)
endif()
message( "LIBPLIST" ${LIBPLIST} )
target_link_libraries( airplay target_link_libraries( airplay
pthread pthread
playfair playfair
llhttp llhttp
${LIBPLIST} ) ${LIBPLIST} )
if( UNIX AND NOT APPLE )
find_package(OpenSSL 1.1.1 REQUIRED) find_package(OpenSSL 1.1.1 REQUIRED)
target_compile_definitions(airplay PUBLIC OPENSSL_API_COMPAT=0x10101000L) target_compile_definitions( airplay PUBLIC OPENSSL_API_COMPAT=0x10101000L )
target_link_libraries( airplay OpenSSL::Crypto ) target_link_libraries( airplay OpenSSL::Crypto )
target_link_libraries( airplay dns_sd ) target_link_libraries( airplay dns_sd )
elseif( APPLE )
# can either compile Openssl 1.1.1 from source (install_dev to /usr/local) or use Macports or Brew
# MacPorts needs zlib with it, Brew has a strange "keg-only" installation in usr/local/opt/openssl@1.1
pkg_check_modules( OPENSSL REQUIRED Openssl>=1.1.1)
message( "OPENSSL_LIBRARY_DIRS " ${OPENSSL_LIBRARY_DIRS} )
message( "OPENSSL_INCLUDE_DIRS " ${OPENSSL_INCLUDE_DIRS} )
find_library( LIBCRYPTO libcrypto.a PATHS ${OPENSSL_LIBRARY_DIRS} REQUIRED )
find_library( LIBZ libz.a) # needed by MacPorts openssl
message( "LIBCRYPTO " ${LIBCRYPTO} )
target_include_directories( airplay PRIVATE
${OPENSSL_INCLUDE_DIRS}
)
target_link_libraries( airplay
${LIBCRYPTO}
${LIBZ}
)
endif()

View File

@@ -1,19 +1,52 @@
cmake_minimum_required(VERSION 3.4.1) cmake_minimum_required(VERSION 3.4.1)
find_package(PkgConfig)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -march=native -DSTANDALONE -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DTARGET_POSIX -D_LINUX -fPIC -DPIC -D_REENTRANT -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -U_FORTIFY_SOURCE -Wall -g")
pkg_check_modules(GST REQUIRED gstreamer-1.0>=1.4
gstreamer-sdp-1.0>=1.4
gstreamer-video-1.0>=1.4
gstreamer-app-1.0>=1.4)
add_library( renderers
STATIC
audio_renderer_gstreamer.c video_renderer_gstreamer.c)
include_directories ( renderers ${GST_INCLUDE_DIRS} )
target_link_libraries ( renderers ${GST_LIBRARIES} )
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -march=native -DSTANDALONE -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -DTARGET_POSIX -D_LINUX -fPIC -DPIC -D_REENTRANT -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -U_FORTIFY_SOURCE -Wall -g")
target_link_libraries (renderers airplay) if (APPLE )
set( ENV{PKG_CONFIG_PATH} "/Library/FrameWorks/GStreamer.framework/Libraries/pkgconfig" ) # GStreamer.framework, preferred
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig" ) # standard location, and Brew
set( ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/opt/local/lib/pkgconfig/" ) # MacPorts
message( "PKG_CONFIG_PATH (Apple, renderers) = " $ENV{PKG_CONFIG_PATH} )
find_program( PKG_CONFIG_EXECUTABLE pkg-config PATHS /Library/FrameWorks/GStreamer.framework/Commands )
endif()
find_package( PkgConfig REQUIRED )
pkg_check_modules(GST REQUIRED gstreamer-1.0>=1.4
gstreamer-sdp-1.0>=1.4
gstreamer-video-1.0>=1.4
gstreamer-app-1.0>=1.4
)
message( "GST_LIBRARIES" ${GST_LIBRARIES} )
message( "GST_LIBRARY_DIRS " ${GST_LIBRARY_DIRS} )
message( "GST_CFLAGS " ${GST_CFLAGS} )
message( "GST_LDFLAGS " ${GST_LDFLAGS} )
message( "GST_INCLUDE_DIRS " ${GST_INCLUDE_DIRS} )
add_library( renderers
STATIC
audio_renderer_gstreamer.c
video_renderer_gstreamer.c )
if( UNIX AND NOT APPLE )
include_directories ( renderers ${GST_INCLUDE_DIRS} )
else()
include_directories (renderers
/Library/FrameWorks/GStreamer.framework/Headers
/usr/local/include
/usr/local/include/gstreamer-1.0
/usr/local/include/glib-2.0
/usr/local/lib/glib-2.0/include
/opt/local/include/gstreamer-1.0
/opt/local/include/glib-2.0
/opt/local/lib/glib-2.0/include
)
endif()
target_link_libraries ( renderers PUBLIC
${GST_LIBRARIES}
airplay
)
if( APPLE )
message( "APPLE ONLY: \"target_link_directories\" used here requires CMake >= 3.13 ")
target_link_directories ( renderers PUBLIC ${GST_LIBRARY_DIRS} )
endif()

View File

@@ -48,7 +48,7 @@ video_renderer_t *video_renderer_init (logger_t *logger, const char *server_name
void video_renderer_start (video_renderer_t *renderer); void video_renderer_start (video_renderer_t *renderer);
void video_renderer_render_buffer (video_renderer_t *renderer, raop_ntp_t *ntp, unsigned char* data, int data_len, uint64_t pts, int type); void video_renderer_render_buffer (video_renderer_t *renderer, raop_ntp_t *ntp, unsigned char* data, int data_len, uint64_t pts, int type);
void video_renderer_flush (video_renderer_t *renderer); void video_renderer_flush (video_renderer_t *renderer);
bool video_renderer_listen (video_renderer_t *renderer); unsigned int video_renderer_listen(void *loop, video_renderer_t *renderer);
void video_renderer_destroy (video_renderer_t *renderer); void video_renderer_destroy (video_renderer_t *renderer);
/* not implemented for gstreamer */ /* not implemented for gstreamer */

View File

@@ -119,7 +119,7 @@ video_renderer_t *video_renderer_init(logger_t *logger, const char *server_name,
video_renderer_t *renderer; video_renderer_t *renderer;
GError *error = NULL; GError *error = NULL;
/* this call to g_set_application_name makes server_name appear in the display window title bar, */ /* this call to g_set_application_name makes server_name appear in the X11 display window title bar, */
/* (instead of the program name uxplay taken from (argv[0]). It is only set one time. */ /* (instead of the program name uxplay taken from (argv[0]). It is only set one time. */
if (!g_get_application_name()) g_set_application_name(server_name); if (!g_get_application_name()) g_set_application_name(server_name);
@@ -184,41 +184,6 @@ void video_renderer_render_buffer(video_renderer_t *renderer, raop_ntp_t *ntp, u
} }
void video_renderer_flush(video_renderer_t *renderer) { void video_renderer_flush(video_renderer_t *renderer) {
}
bool video_renderer_listen(video_renderer_t *renderer) {
GstMessage *msg = NULL;
/* listen on the gstreamer pipeline bus for an error or EOS. */
/* return true if this occurs, and false if 100 millisecs have */
/* elapsed with no such event occuring. */
msg = gst_bus_timed_pop_filtered(renderer->bus, 100 * GST_MSECOND,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* parse message */
if (msg != NULL) {
GError *err;
gchar *debug_info;
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error (msg, &err, &debug_info);
g_printerr("GStreamer: %s\n", err->message);
g_clear_error (&err);
g_free (debug_info);
break;
case GST_MESSAGE_EOS:
g_print("End-Of-Stream reached.\n");
break;
default:
g_printerr("unexpected message\n");
break;
}
gst_message_unref(msg);
return true;
}
return false;
} }
void video_renderer_destroy(video_renderer_t *renderer) { void video_renderer_destroy(video_renderer_t *renderer) {
@@ -237,3 +202,32 @@ void video_renderer_destroy(video_renderer_t *renderer) {
/* not implemented for gstreamer */ /* not implemented for gstreamer */
void video_renderer_update_background(video_renderer_t *renderer, int type) { void video_renderer_update_background(video_renderer_t *renderer, int type) {
} }
gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, gpointer loop) {
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_ERROR: {
GError *err;
gchar *debug;
gst_message_parse_error (message, &err, &debug);
g_print ("GStreamer error: %s\n", err->message);
g_error_free (err);
g_free (debug);
g_main_loop_quit( (GMainLoop *) loop);
break;
}
case GST_MESSAGE_EOS:
/* end-of-stream */
g_print("GStreamer: End-Of-Stream\n");
g_main_loop_quit( (GMainLoop *) loop);
break;
default:
/* unhandled message */
break;
}
return TRUE;
}
unsigned int video_renderer_listen(void *loop, video_renderer_t *renderer) {
return (unsigned int) gst_bus_add_watch(renderer->bus, (GstBusFunc)
gstreamer_pipeline_bus_callback, (gpointer) loop);
}

View File

@@ -24,6 +24,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <fstream> #include <fstream>
#include <glib-unix.h>
#include "log.h" #include "log.h"
#include "lib/raop.h" #include "lib/raop.h"
@@ -33,48 +34,73 @@
#include "renderers/video_renderer.h" #include "renderers/video_renderer.h"
#include "renderers/audio_renderer.h" #include "renderers/audio_renderer.h"
#define VERSION "1.341" #define VERSION "1.35"
#define DEFAULT_NAME "UxPlay" #define DEFAULT_NAME "UxPlay"
#define DEFAULT_DEBUG_LOG false #define DEFAULT_DEBUG_LOG false
#define LOWEST_ALLOWED_PORT 1024 #define LOWEST_ALLOWED_PORT 1024
#define HIGHEST_PORT 65535 #define HIGHEST_PORT 65535
static int start_server (std::vector<char> hw_addr, std::string name, unsigned short display[5], static int start_server (std::vector<char> hw_addr, std::string name, unsigned short display[5],
unsigned short tcp[3], unsigned short udp[3], videoflip_t videoflip[2], unsigned short tcp[3], unsigned short udp[3], videoflip_t videoflip[2],
bool use_audio, bool debug_log, std::string videosink); bool use_audio, bool debug_log, std::string videosink);
static int stop_server (); static int stop_server ();
static bool running = false;
static uint open_connections = 0;
static bool had_connection = false;
static dnssd_t *dnssd = NULL; static dnssd_t *dnssd = NULL;
static raop_t *raop = NULL; static raop_t *raop = NULL;
static video_renderer_t *video_renderer = NULL; static video_renderer_t *video_renderer = NULL;
static audio_renderer_t *audio_renderer = NULL; static audio_renderer_t *audio_renderer = NULL;
static logger_t *render_logger = NULL; static logger_t *render_logger = NULL;
static void signal_handler (int sig) { static bool relaunch_server = false;
switch (sig) { static uint open_connections = 0;
case SIGINT: static bool connections_stopped = false;
case SIGTERM: static unsigned int server_timeout = 0;
running = 0; static unsigned int counter;
break;
gboolean connection_callback (gpointer loop){
if (!connections_stopped) {
counter = 0;
} else {
if (++counter == server_timeout) {
LOGI("no connections for %d seconds: relaunch server\n",server_timeout);
g_main_loop_quit((GMainLoop *) loop);
}
} }
return TRUE;
}
static gboolean sigint_callback(gpointer loop) {
relaunch_server = false;
g_main_loop_quit((GMainLoop *) loop);
return TRUE;
} }
static void init_signals (void) { static gboolean sigterm_callback(gpointer loop) {
struct sigaction sigact; relaunch_server = false;
g_main_loop_quit((GMainLoop *) loop);
sigact.sa_handler = signal_handler; return TRUE;
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = 0;
sigaction(SIGINT, &sigact, NULL);
sigaction(SIGTERM, &sigact, NULL);
} }
static void main_loop() {
guint connection_watch_id = 0;
GMainLoop *loop = g_main_loop_new(NULL,FALSE);
if (server_timeout) {
connection_watch_id = g_timeout_add_seconds(1, (GSourceFunc) connection_callback, (gpointer) loop);
}
guint gst_bus_watch_id = (guint) video_renderer_listen((void *)loop, video_renderer);
guint sigterm_watch_id = g_unix_signal_add(SIGTERM, (GSourceFunc) sigterm_callback, (gpointer) loop);
guint sigint_watch_id = g_unix_signal_add(SIGINT, (GSourceFunc) sigint_callback, (gpointer) loop);
relaunch_server = true;
g_main_loop_run(loop);
if (gst_bus_watch_id > 0) g_source_remove(gst_bus_watch_id);
if (sigint_watch_id > 0) g_source_remove(sigint_watch_id);
if (sigterm_watch_id > 0) g_source_remove(sigterm_watch_id);
if (connection_watch_id > 0) g_source_remove(connection_watch_id);
g_main_loop_unref(loop);
}
static int parse_hw_addr (std::string str, std::vector<char> &hw_addr) { static int parse_hw_addr (std::string str, std::vector<char> &hw_addr) {
for (int i = 0; i < str.length(); i += 3) { for (int i = 0; i < str.length(); i += 3) {
hw_addr.push_back((char) stol(str.substr(i), NULL, 16)); hw_addr.push_back((char) stol(str.substr(i), NULL, 16));
@@ -123,14 +149,15 @@ static void print_info (char *name) {
printf("-o Set mirror \"overscanned\" mode on (not usually needed)\n"); printf("-o Set mirror \"overscanned\" mode on (not usually needed)\n");
printf("-fps n Set maximum allowed streaming framerate, default 30\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("-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("-r {R|L} Rotate 90 degrees Right (cw) or Left (ccw)\n");
printf("-p Use legacy ports UDP 6000:6001:7011 TCP 7000:7001:7100\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("-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"); 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(" \"-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("-m Use random MAC address (use for concurrent UxPlay's)\n");
printf("-a Turn audio off. video output only\n"); printf("-a Turn audio off. video output only\n");
printf("-vs choose the GStreamer videosink; default \"autovideosink\"\n"); printf("-t n Relaunch server if no connection existed in last n seconds\n");
printf("-vs Choose the GStreamer videosink; default \"autovideosink\"\n");
printf(" choices: ximagesink,xvimagesink,vaapisink,fpsdisplaysink, etc.\n"); printf(" choices: ximagesink,xvimagesink,vaapisink,fpsdisplaysink, etc.\n");
printf("-d Enable debug logging\n"); printf("-d Enable debug logging\n");
printf("-v/-h Displays this help and version information\n"); printf("-v/-h Displays this help and version information\n");
@@ -169,12 +196,13 @@ static bool get_display_settings (std::string value, unsigned short *w, unsigned
return true; return true;
} }
static bool get_fps (const char *str, unsigned short *n) { static bool get_value (const char *str, unsigned int *n) {
// str must be a positive decimal integer < 256 (stored in one byte) // str must be a positive decimal <= input value *n
if (strlen(str) == 0 || strlen(str) > 10 || str[0] == '-') return false;
char *end; char *end;
if (strlen(str) == 0 || strlen(str) > 3 || str[0] == '-') return false; unsigned long l = strtoul(str, &end, 10);
*n = (unsigned short) strtoul(str, &end, 10); if (*end || l == 0 || (*n > 0 && l > *n)) return false;
if (*end || *n == 0 || *n > 255) return false; *n = (unsigned int) l;
return true; return true;
} }
@@ -245,8 +273,6 @@ static bool get_videorotate (const char *str, videoflip_t *videoflip) {
} }
int main (int argc, char *argv[]) { int main (int argc, char *argv[]) {
init_signals();
std::string server_name = DEFAULT_NAME; std::string server_name = DEFAULT_NAME;
std::vector<char> server_hw_addr; std::vector<char> server_hw_addr;
bool use_audio = true; bool use_audio = true;
@@ -279,10 +305,12 @@ int main (int argc, char *argv[]) {
} }
} else if (arg == "-fps") { } else if (arg == "-fps") {
if (!option_has_value(i, argc, arg, argv[i+1])) exit(1); if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
if (!get_fps(argv[++i], &display[3])) { unsigned int n = 255;
if (!get_value(argv[++i], &n)) {
fprintf(stderr, "invalid \"-fps %s\"; -fps n : max n=255, default n=30\n", argv[i]); fprintf(stderr, "invalid \"-fps %s\"; -fps n : max n=255, default n=30\n", argv[i]);
exit(1); exit(1);
} }
display[3] = (unsigned short) n;
} else if (arg == "-o") { } else if (arg == "-o") {
display[4] = 1; display[4] = 1;
} else if (arg == "-f") { } else if (arg == "-f") {
@@ -329,7 +357,11 @@ int main (int argc, char *argv[]) {
if (!option_has_value(i, argc, arg, argv[i+1])) exit(1); if (!option_has_value(i, argc, arg, argv[i+1])) exit(1);
videosink.erase(); videosink.erase();
videosink.append(argv[++i]); videosink.append(argv[++i]);
} else { } 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);
} else {
LOGE("unknown option %s, stopping\n",argv[i]); LOGE("unknown option %s, stopping\n",argv[i]);
exit(1); exit(1);
} }
@@ -349,28 +381,27 @@ int main (int argc, char *argv[]) {
mac_address.clear(); mac_address.clear();
relaunch: relaunch:
had_connection = false; connections_stopped = false;
if (start_server(server_hw_addr, server_name, display, tcp, udp, if (start_server(server_hw_addr, server_name, display, tcp, udp,
videoflip,use_audio, debug_log, videosink)) { videoflip,use_audio, debug_log, videosink)) {
return 1; return 1;
} }
running = true;
while (running) {
if ((video_renderer_listen(video_renderer)) || (had_connection && !open_connections)) {
stop_server();
LOGI("Re-launching server...");
goto relaunch;
}
}
LOGI("Stopping..."); main_loop();
stop_server(); if (relaunch_server) {
LOGI("Re-launching server...");
stop_server();
goto relaunch;
} else {
LOGI("Stopping...");
stop_server();
}
} }
// Server callbacks // Server callbacks
extern "C" void conn_init (void *cls) { extern "C" void conn_init (void *cls) {
open_connections++; open_connections++;
had_connection = true; connections_stopped = false;
LOGI("Open connections: %i", open_connections); LOGI("Open connections: %i", open_connections);
video_renderer_update_background(video_renderer, 1); video_renderer_update_background(video_renderer, 1);
} }
@@ -379,6 +410,9 @@ extern "C" void conn_destroy (void *cls) {
video_renderer_update_background(video_renderer, -1); video_renderer_update_background(video_renderer, -1);
open_connections--; open_connections--;
LOGI("Open connections: %i", open_connections); LOGI("Open connections: %i", open_connections);
if(!open_connections) {
connections_stopped = true;
}
} }
extern "C" void audio_process (void *cls, raop_ntp_t *ntp, aac_decode_struct *data) { extern "C" void audio_process (void *cls, raop_ntp_t *ntp, aac_decode_struct *data) {
@@ -460,11 +494,13 @@ int start_server (std::vector<char> hw_addr, std::string name, unsigned short di
raop_set_log_level(raop, debug_log ? RAOP_LOG_DEBUG : LOGGER_INFO); raop_set_log_level(raop, debug_log ? RAOP_LOG_DEBUG : LOGGER_INFO);
render_logger = logger_init(); render_logger = logger_init();
if (render_logger == NULL){ if (render_logger == NULL) {
LOGE("Count not init render_logger\n"); LOGE("Could not init render_logger\n");
stop_server(); stop_server();
return -1; return -1;
} }
logger_set_callback(render_logger, log_callback, NULL); logger_set_callback(render_logger, log_callback, NULL);
logger_set_level(render_logger, debug_log ? LOGGER_DEBUG : LOGGER_INFO); logger_set_level(render_logger, debug_log ? LOGGER_DEBUG : LOGGER_INFO);