mirror of
https://github.com/morgan9e/UxPlay
synced 2026-04-14 00:04:13 +09:00
version now 2.35; updated to build on MacOs. Now uses a Glib MainLoop.
This commit is contained in:
@@ -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)
|
||||
add_definitions(-DX_DISPLAY_FIX)
|
||||
find_package(X11 REQUIRED)
|
||||
link_libraries(${X11_LIBRARIES})
|
||||
include_directories(${X11_INCLUDE_DIR})
|
||||
# link_directories(${X11_LIBRARIES})
|
||||
endif (ZOOMFIX)
|
||||
if( UNIX AND NOT APPLE )
|
||||
add_definitions( -DSUPPRESS_AVAHI_COMPAT_WARNING )
|
||||
endif()
|
||||
|
||||
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)
|
||||
add_subdirectory(lib/playfair)
|
||||
add_subdirectory(lib)
|
||||
add_subdirectory(renderers)
|
||||
if( UNIX AND NOT APPLE )
|
||||
include_directories( uxplay ${GST_INCLUDE_DIRS} )
|
||||
else()
|
||||
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 renderers airplay ${X11_LIBRARIES})
|
||||
|
||||
install(TARGETS uxplay RUNTIME DESTINATION bin)
|
||||
target_link_libraries( uxplay
|
||||
renderers
|
||||
airplay
|
||||
)
|
||||
|
||||
install( TARGETS uxplay RUNTIME DESTINATION bin )
|
||||
|
||||
120
README.md
120
README.md
@@ -1,13 +1,13 @@
|
||||
This project is an early stage prototype of unix AirPlay server.
|
||||
Work is based on https://github.com/FD-/RPiPlay.
|
||||
This project is a unix AirPlay server which now also works on MacOs.
|
||||
The work is based on https://github.com/FD-/RPiPlay.
|
||||
Tested on Ubuntu 19.10 desktop.
|
||||
5G Wifi connection is the must.
|
||||
Tested on MacOS 10.15
|
||||
|
||||
Features:
|
||||
1. Based on Gstreamer.
|
||||
2. Video and audio are supported out of the box.
|
||||
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.
|
||||
|
||||
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
|
||||
get committed into the codebase on the original antimof site, as the antimof
|
||||
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
|
||||
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
|
||||
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
|
||||
5. cd build
|
||||
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-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:**
|
||||
|
||||
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:
|
||||
**-n server_name **; server_name will be the name that appears offering
|
||||
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,
|
||||
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
|
||||
addresses, and network ports are needed for each running uxplay if you
|
||||
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.
|
||||
|
||||
@@ -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)
|
||||
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
|
||||
fpsdisplaysink (which shows the streaming framerate in fps). Using quotes
|
||||
"..." might allow some parameters to be included with the videosink name.
|
||||
(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
|
||||
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
|
||||
first time the GStreamer window was created by uxplay, and
|
||||
not if the server was relaunched after the GStreamer window
|
||||
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
|
||||
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
|
||||
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,
|
||||
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
|
||||
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).
|
||||
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
|
||||
"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
|
||||
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.
|
||||
is compiled.) On MacOS, Avahi is not used.
|
||||
|
||||
# Disclaimer
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
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)
|
||||
set(DIR_SRCS ${play_src})
|
||||
|
||||
@@ -11,17 +20,46 @@ add_library( airplay
|
||||
${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
|
||||
pthread
|
||||
playfair
|
||||
llhttp
|
||||
${LIBPLIST} )
|
||||
|
||||
if( UNIX AND NOT APPLE )
|
||||
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 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()
|
||||
|
||||
@@ -1,19 +1,52 @@
|
||||
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()
|
||||
|
||||
@@ -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_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);
|
||||
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);
|
||||
|
||||
/* not implemented for gstreamer */
|
||||
|
||||
@@ -119,7 +119,7 @@ video_renderer_t *video_renderer_init(logger_t *logger, const char *server_name,
|
||||
video_renderer_t *renderer;
|
||||
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. */
|
||||
|
||||
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) {
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -237,3 +202,32 @@ void video_renderer_destroy(video_renderer_t *renderer) {
|
||||
/* not implemented for gstreamer */
|
||||
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);
|
||||
}
|
||||
|
||||
126
uxplay.cpp
126
uxplay.cpp
@@ -24,6 +24,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <glib-unix.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "lib/raop.h"
|
||||
@@ -33,48 +34,73 @@
|
||||
#include "renderers/video_renderer.h"
|
||||
#include "renderers/audio_renderer.h"
|
||||
|
||||
#define VERSION "1.341"
|
||||
#define VERSION "1.35"
|
||||
|
||||
#define DEFAULT_NAME "UxPlay"
|
||||
#define DEFAULT_DEBUG_LOG false
|
||||
#define LOWEST_ALLOWED_PORT 1024
|
||||
#define HIGHEST_PORT 65535
|
||||
|
||||
|
||||
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],
|
||||
bool use_audio, bool debug_log, std::string videosink);
|
||||
|
||||
static int stop_server ();
|
||||
|
||||
static bool running = false;
|
||||
static uint open_connections = 0;
|
||||
static bool had_connection = false;
|
||||
static dnssd_t *dnssd = NULL;
|
||||
static raop_t *raop = NULL;
|
||||
static video_renderer_t *video_renderer = NULL;
|
||||
static audio_renderer_t *audio_renderer = NULL;
|
||||
static logger_t *render_logger = NULL;
|
||||
|
||||
static void signal_handler (int sig) {
|
||||
switch (sig) {
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
running = 0;
|
||||
break;
|
||||
static bool relaunch_server = false;
|
||||
static uint open_connections = 0;
|
||||
static bool connections_stopped = false;
|
||||
static unsigned int server_timeout = 0;
|
||||
static unsigned int counter;
|
||||
|
||||
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) {
|
||||
struct sigaction sigact;
|
||||
|
||||
sigact.sa_handler = signal_handler;
|
||||
sigemptyset(&sigact.sa_mask);
|
||||
sigact.sa_flags = 0;
|
||||
sigaction(SIGINT, &sigact, NULL);
|
||||
sigaction(SIGTERM, &sigact, NULL);
|
||||
static gboolean sigterm_callback(gpointer loop) {
|
||||
relaunch_server = false;
|
||||
g_main_loop_quit((GMainLoop *) loop);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
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) {
|
||||
for (int i = 0; i < str.length(); i += 3) {
|
||||
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("-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("-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 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(" \"-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("-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("-d Enable debug logging\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;
|
||||
}
|
||||
|
||||
static bool get_fps (const char *str, unsigned short *n) {
|
||||
// str must be a positive decimal integer < 256 (stored in one byte)
|
||||
static bool get_value (const char *str, unsigned int *n) {
|
||||
// str must be a positive decimal <= input value *n
|
||||
if (strlen(str) == 0 || strlen(str) > 10 || str[0] == '-') return false;
|
||||
char *end;
|
||||
if (strlen(str) == 0 || strlen(str) > 3 || str[0] == '-') return false;
|
||||
*n = (unsigned short) strtoul(str, &end, 10);
|
||||
if (*end || *n == 0 || *n > 255) return false;
|
||||
unsigned long l = strtoul(str, &end, 10);
|
||||
if (*end || l == 0 || (*n > 0 && l > *n)) return false;
|
||||
*n = (unsigned int) l;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -245,8 +273,6 @@ static bool get_videorotate (const char *str, videoflip_t *videoflip) {
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
init_signals();
|
||||
|
||||
std::string server_name = DEFAULT_NAME;
|
||||
std::vector<char> server_hw_addr;
|
||||
bool use_audio = true;
|
||||
@@ -279,10 +305,12 @@ int main (int argc, char *argv[]) {
|
||||
}
|
||||
} else if (arg == "-fps") {
|
||||
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]);
|
||||
exit(1);
|
||||
}
|
||||
display[3] = (unsigned short) n;
|
||||
} else if (arg == "-o") {
|
||||
display[4] = 1;
|
||||
} 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);
|
||||
videosink.erase();
|
||||
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]);
|
||||
exit(1);
|
||||
}
|
||||
@@ -349,28 +381,27 @@ int main (int argc, char *argv[]) {
|
||||
mac_address.clear();
|
||||
|
||||
relaunch:
|
||||
had_connection = false;
|
||||
connections_stopped = false;
|
||||
if (start_server(server_hw_addr, server_name, display, tcp, udp,
|
||||
videoflip,use_audio, debug_log, videosink)) {
|
||||
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...");
|
||||
stop_server();
|
||||
main_loop();
|
||||
if (relaunch_server) {
|
||||
LOGI("Re-launching server...");
|
||||
stop_server();
|
||||
goto relaunch;
|
||||
} else {
|
||||
LOGI("Stopping...");
|
||||
stop_server();
|
||||
}
|
||||
}
|
||||
|
||||
// Server callbacks
|
||||
extern "C" void conn_init (void *cls) {
|
||||
open_connections++;
|
||||
had_connection = true;
|
||||
connections_stopped = false;
|
||||
LOGI("Open connections: %i", open_connections);
|
||||
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);
|
||||
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) {
|
||||
@@ -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);
|
||||
|
||||
render_logger = logger_init();
|
||||
if (render_logger == NULL){
|
||||
LOGE("Count not init render_logger\n");
|
||||
if (render_logger == NULL) {
|
||||
LOGE("Could not init render_logger\n");
|
||||
stop_server();
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
logger_set_callback(render_logger, log_callback, NULL);
|
||||
logger_set_level(render_logger, debug_log ? LOGGER_DEBUG : LOGGER_INFO);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user