Merge pull request #87 from FDH2/master

Preparation for next release 1.52:
This commit is contained in:
antimof
2022-05-07 01:46:16 +03:00
committed by GitHub
14 changed files with 143 additions and 64 deletions

View File

@@ -1,9 +1,9 @@
<h1 id="uxplay-1.51-airplayairplay-mirror-server-for-linux-macos-and-unix.">UxPlay 1.51: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.</h1>
<h1 id="uxplay-1.52-airplayairplay-mirror-server-for-linux-macos-and-unix.">UxPlay 1.52: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.</h1>
<h3 id="now-developed-at-github-site-httpsgithub.comfdh2uxplay-where-user-issues-should-be-posted.">Now developed at GitHub site <a href="https://github.com/FDH2/UxPlay">https://github.com/FDH2/UxPlay</a> (where user issues should be posted).</h3>
<p>Highlights:</p>
<ul>
<li>GPLv3, open source.</li>
<li>Support for both AirPlay Mirror and AirPlay Audio-only (Apple Lossless ALAC) protocols from current iOS/iPadOS 15.2 client devices.</li>
<li>Support for both AirPlay Mirror and AirPlay Audio-only (Apple Lossless ALAC) protocols from current iOS/iPadOS 15.4 client devices.</li>
<li>macOS computers (2011 or later) can act either as AirPlay clients, or as the server running UxPlay (tested on macOS 10.15 Catalina and 12.3 Monterey). Using AirPlay, UxPlay can emulate a second display for macOS clients. Both Intel and “Apple Silicon” M1 Macs are now fully supported in both roles.</li>
<li>Support for older 32-bit iOS clients (such as iPad 2nd gen, iPhone 4S, when upgraded to iOS 9.3.5 or later), and a Windows AirPlay-client emulator, AirMyPC.</li>
<li>Uses GStreamer, with options to select different output “videosinks” and “audiosinks”.</li>
@@ -42,11 +42,11 @@
<li><code>cmake .</code> (or “<code>cmake -DZOOMFIX=ON .</code>” to get a screen-sharing fix to make X11 mirror display windows visible to screen-sharing applications such as Zoom, see <a href="#improvements">Improvements</a> #3 below). <strong>ZOOMFIX is only needed for GStreamer-1.18.x or earlier</strong>.</li>
<li><code>make</code></li>
<li><code>sudo make install</code> (you can afterwards uninstall with <code>sudo make uninstall</code> in the same directory in which this was run)</li>
<li>Install GStreamer plugins that you need: <code>sudo apt-get install gstreamer1.0-&lt;plugin&gt;</code>; values of <code>&lt;plugin&gt;</code> needed are: “plugins-base”, “libav” (for sound), “plugins-good” (for v4l2 hardware h264 decoding) and “plugins-bad” (for h264 decoding). Also needed may be “gl” for OpenGL support, and “x” for X11 support, although these may already be installed; “vaapi” is needed for hardware-accelerated h264 video decoding by Intel graphics (not for NVIDIA). Also install “tools” to get the utility gst-inspect-1.0 for examining the GStreamer installation.</li>
<li>Install GStreamer plugins that you need: <code>sudo apt-get install gstreamer1.0-&lt;plugin&gt;</code>; values of <code>&lt;plugin&gt;</code> needed are: “<strong>plugins-base</strong>”, “<strong>libav</strong>” (for sound), “<strong>plugins-good</strong>” (for v4l2 hardware h264 decoding) and “<strong>plugins-bad</strong>” (for h264 decoding). Also needed may be “<strong>gl</strong>” for OpenGL support (which may be useful, and should be used with h264 decoding by the NVIDIA GPU), and “<strong>x</strong>” for X11 support, although these may already be installed; “<strong>vaapi</strong>” is needed for hardware-accelerated h264 video decoding by Intel or AMD graphics (but not for use with NVIDIA using proprietary drivers). Also install “<strong>tools</strong>” to get the utility gst-inspect-1.0 for examining the GStreamer installation.</li>
</ol>
<p><em>If you intend to modify the code, use a separate “build” directory: replace</em><code>cmake [ ] .</code><em>by</em><code>mkdir build ; cd build ; cmake [ ] ..</code>”; <em>you can then clean the build directory with</em><code>rm -rf build/*</code><em>(run from within the UxPlay source directory) without affecting the source directories which contain your modifications</em>.</p>
<p>The above script installs the executable file “<code>uxplay</code>” to <code>/usr/local/bin</code>, (and installs a manpage to somewhere like <code>/usr/local/share/man/man1</code> and README files to somewhere like <code>/usr/local/share/doc/uxplay</code>). It can also be found in the build directory after the build processs.</p>
<p><strong>Finally, run uxplay in a terminal window</strong>. If it is not seen by the iOS clients drop-down “Screen Mirroring” panel, check that your DNS-SD server (usually avahi-daemon) is running: do this in a terminal window with <code>systemctl status avahi-daemon</code>. If this shows the avahi-daemon is not running, control it with <code>sudo systemctl [start,stop,enable,disable] avahi-daemon</code> (or avahi-daemon.service). If UxPlay is seen, but the client fails to connect when it is selected, there may be a firewall on the server that prevents UxPlay from receiving client connection requests unless some network ports are opened. See <a href="#troubleshooting">Troubleshooting</a> below for help with this or other problems. See <a href="#usage">Usage</a> for run-time options. For OpenGL support (option -vs glimagesink), needed for Raspberry Pi and NVIDIA GPU-based video decoding, make sure gstreamer1.0-gl is installed.</p>
<p><strong>Finally, run uxplay in a terminal window</strong>. If it is not seen by the iOS clients drop-down “Screen Mirroring” panel, check that your DNS-SD server (usually avahi-daemon) is running: do this in a terminal window with <code>systemctl status avahi-daemon</code>. If this shows the avahi-daemon is not running, control it with <code>sudo systemctl [start,stop,enable,disable] avahi-daemon</code> (or avahi-daemon.service). If UxPlay is seen, but the client fails to connect when it is selected, there may be a firewall on the server that prevents UxPlay from receiving client connection requests unless some network ports are opened. See <a href="#troubleshooting">Troubleshooting</a> below for help with this or other problems. See <a href="#usage">Usage</a> for run-time options.</p>
<ul>
<li><p><strong>Red Hat, Fedora, CentOS (now continued as Rocky Linux or Alma Linux):</strong> (sudo yum install) openssl-devel libplist-devel avahi-compat-libdns_sd-devel (some from the “PowerTools” add-on repository) (+libX11-devel for ZOOMFIX). The required GStreamer packages (some from <a href="https://rpmfusion.org">rpmfusion.org</a>) are: gstreamer1-devel gstreamer1-plugins-base-devel gstreamer1-libav gstreamer1-plugins-bad-free (+ gstreamer1-vaapi for intel graphics).</p></li>
<li><p><strong>OpenSUSE:</strong> (sudo zypper install) libopenssl-devel libplist-devel avahi-compat-mDNSResponder-devel (+ libX11-devel for ZOOMFIX). The required GStreamer packages (you may need to use versions from <a href="https://ftp.gwdg.de/pub/linux/misc/packman/suse/">Packman</a>) are: gstreamer-devel gstreamer-plugins-base-devel gstreamer-plugins-libav gstreamer-plugins-bad (+ gstreamer-plugins-vaapi for Intel graphics).</p></li>
@@ -139,6 +139,7 @@
<p>This triggers an unending stream of error messages, and means that the audio decryption key (also used in video decryption) was not correctly extracted from data sent by the client. This should not happen for iOS 9.3 or later clients. However, if a client uses the same older version of the protocol that is used by the Windows-based AirPlay client emulator <em>AirMyPC</em>, the protocol can be switched to the older version by the setting <code>OLD_PROTOCOL_CLIENT_USER_AGENT_LIST</code> in lib/global.h. UxPlay reports the clients “User Agent” string when it connects. If some other client also fails to decrypt all audio and video, try adding its “User Agent” string in place of “xxx” in the entry “AirMyPC/2.0;xxx” in global.h and rebuild uxplay.</p>
<p>Note that Uxplay declares itself to be an AppleTV3,2 with a sourceVersion 220.68; this can also be changed in global.h. It had been thought that it was necessary for UxPlay to claim to be an older 32 bit AppleTV model that cannot run modern 64bit tvOS, in order for the client to use a “legacy” protocol for pairing with the server (see the <em>“Notes on AirPlay protocol versions”</em> at the end of this README). However, UxPlay still works if it declares itself as an AppleTV6,2 with sourceVersion 380.20.1 (an AppleTV 4K 1st gen, introduced 2017, running tvOS 12.2.1), so it is unclear what setting prompts the client to use the “legacy” protocol needed by UxPlay.</p>
<h1 id="changelog">ChangeLog</h1>
<p>1.52 2022-05-05 Cleaned up initial audio sync code, and reformatted streaming debug output (readable aligned timestamps with decimal points in seconds). Eliminate memory leaks (found by valgrind).</p>
<p>1.51 2022-04-24 Reworked options forVideo4Linux2 support (new option -v4l2) and short options -rpi, -rpifb, -rpiwl as synonyms for -v4l2, -v4l2 -vs kmssink, and -v4l2 -vs waylandsink. Reverted a change from 1.48 that broke reconnection after “Stop Mirroring” is sent by client.</p>
<p>1.50 2022-04-22 Added -fs fullscreen option (for Wayland or VAAPI plugins only), Changed -rpi to be for framebuffer (“lite”) RPi systems and added -rpigl (OpenGL) and -rpiwl (Wayland) options for RPi Desktop systems. Also modified timestamps from “DTS” to “PTS” for latency improvement, plus internal cleanups.</p>
<p>1.49 2022-03-28 Addded options for dumping video and/or audio to file, for debugging, etc. h264 PPS/SPS NALUs are shown with -d. Fixed video-not-working for M1 Mac clients.</p>

View File

@@ -1,4 +1,4 @@
# UxPlay 1.51: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
# UxPlay 1.52: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
### Now developed at GitHub site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where user issues should be posted).
@@ -7,7 +7,7 @@ Highlights:
* GPLv3, open source.
* Support for both AirPlay Mirror and AirPlay Audio-only (Apple Lossless ALAC) protocols
from current iOS/iPadOS 15.2 client devices.
from current iOS/iPadOS 15.4 client devices.
* macOS computers (2011 or later) can act either as AirPlay clients, or as the server running UxPlay (tested
on macOS 10.15 Catalina and 12.3 Monterey). Using AirPlay, UxPlay can emulate a second display for macOS clients.
Both Intel and "Apple Silicon" M1 Macs are now fully supported in both roles.
@@ -156,11 +156,11 @@ for GStreamer-1.18.x or earlier**.
5. `make`
6. `sudo make install` (you can afterwards uninstall with `sudo make uninstall` in the same directory in which this was run)
7. Install GStreamer plugins that you need: `sudo apt-get install gstreamer1.0-<plugin>`; values of
`<plugin>` needed are: "plugins-base", "libav" (for sound), "plugins-good" (for v4l2 hardware h264 decoding)
and "plugins-bad" (for h264 decoding). Also needed may be "gl" for OpenGL support, and "x" for X11 support, although
these may already be installed; "vaapi" is needed for hardware-accelerated h264 video decoding by Intel graphics (not for NVIDIA).
Also install "tools" to get the utility gst-inspect-1.0 for examining the GStreamer installation.
`<plugin>` needed are: "**plugins-base**", "**libav**" (for sound), "**plugins-good**" (for v4l2 hardware h264 decoding)
and "**plugins-bad**" (for h264 decoding). Also needed may be "**gl**" for OpenGL support (which may be useful, and should
be used with h264 decoding by the NVIDIA GPU), and "**x**" for X11 support, although these may already be installed; "**vaapi**"
is needed for hardware-accelerated h264 video decoding by Intel or AMD graphics (but not for use with NVIDIA using proprietary drivers).
Also install "**tools**" to get the utility gst-inspect-1.0 for examining the GStreamer installation.
_If you intend to modify the code, use a separate "build" directory: replace_ "```cmake [ ] . ```" _by_ "```mkdir build ; cd build ; cmake [ ] ..```"; _you can then clean
the build directory with_ "```rm -rf build/* ```" _(run from within the UxPlay source directory) without affecting the source directories which contain your modifications_.
@@ -175,8 +175,7 @@ check that your DNS-SD server (usually avahi-daemon) is running: do this in a te
If this shows the avahi-daemon is not running, control it with ```sudo systemctl [start,stop,enable,disable] avahi-daemon``` (or avahi-daemon.service).
If UxPlay is seen, but the client fails to connect when it is selected, there may be a firewall on the server that prevents
UxPlay from receiving client connection requests unless some network ports are opened. See [Troubleshooting](#troubleshooting) below for
help with this or other problems. See [Usage](#usage) for run-time options. For OpenGL support (option -vs glimagesink), needed for
Raspberry Pi and NVIDIA GPU-based video decoding, make sure gstreamer1.0-gl is installed.
help with this or other problems. See [Usage](#usage) for run-time options.
* **Red Hat, Fedora, CentOS (now continued as Rocky Linux or Alma Linux):**
(sudo yum install) openssl-devel libplist-devel avahi-compat-libdns_sd-devel (some from the "PowerTools" add-on repository)
@@ -595,6 +594,9 @@ tvOS 12.2.1), so it is unclear what setting prompts the client
to use the "legacy" protocol needed by UxPlay.
# ChangeLog
1.52 2022-05-05 Cleaned up initial audio sync code, and reformatted streaming debug output (readable aligned timestamps with
decimal points in seconds). Eliminate memory leaks (found by valgrind).
1.51 2022-04-24 Reworked options forVideo4Linux2 support (new option -v4l2) and short options -rpi, -rpifb, -rpiwl as
synonyms for -v4l2, -v4l2 -vs kmssink, and -v4l2 -vs waylandsink. Reverted a change from 1.48 that broke
reconnection after "Stop Mirroring" is sent by client.

View File

@@ -1,4 +1,4 @@
UxPlay 1.51: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
UxPlay 1.52: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.
======================================================================
### Now developed at GitHub site <https://github.com/FDH2/UxPlay> (where user issues should be posted).
@@ -7,7 +7,7 @@ Highlights:
- GPLv3, open source.
- Support for both AirPlay Mirror and AirPlay Audio-only (Apple
Lossless ALAC) protocols from current iOS/iPadOS 15.2 client
Lossless ALAC) protocols from current iOS/iPadOS 15.4 client
devices.
- macOS computers (2011 or later) can act either as AirPlay clients,
or as the server running UxPlay (tested on macOS 10.15 Catalina and
@@ -203,13 +203,16 @@ packaging for a distribution, use the cmake option
`sudo make uninstall` in the same directory in which this was run)
7. Install GStreamer plugins that you need:
`sudo apt-get install gstreamer1.0-<plugin>`; values of `<plugin>`
needed are: "plugins-base", "libav" (for sound), "plugins-good" (for
v4l2 hardware h264 decoding) and "plugins-bad" (for h264 decoding).
Also needed may be "gl" for OpenGL support, and "x" for X11 support,
although these may already be installed; "vaapi" is needed for
hardware-accelerated h264 video decoding by Intel graphics (not for
NVIDIA). Also install "tools" to get the utility gst-inspect-1.0 for
examining the GStreamer installation.
needed are: "**plugins-base**", "**libav**" (for sound),
"**plugins-good**" (for v4l2 hardware h264 decoding) and
"**plugins-bad**" (for h264 decoding). Also needed may be "**gl**"
for OpenGL support (which may be useful, and should be used with
h264 decoding by the NVIDIA GPU), and "**x**" for X11 support,
although these may already be installed; "**vaapi**" is needed for
hardware-accelerated h264 video decoding by Intel or AMD graphics
(but not for use with NVIDIA using proprietary drivers). Also
install "**tools**" to get the utility gst-inspect-1.0 for examining
the GStreamer installation.
*If you intend to modify the code, use a separate "build" directory:
replace* "`cmake [ ] .`" *by*
@@ -235,9 +238,7 @@ connect when it is selected, there may be a firewall on the server that
prevents UxPlay from receiving client connection requests unless some
network ports are opened. See [Troubleshooting](#troubleshooting) below
for help with this or other problems. See [Usage](#usage) for run-time
options. For OpenGL support (option -vs glimagesink), needed for
Raspberry Pi and NVIDIA GPU-based video decoding, make sure
gstreamer1.0-gl is installed.
options.
- **Red Hat, Fedora, CentOS (now continued as Rocky Linux or Alma
Linux):** (sudo yum install) openssl-devel libplist-devel
@@ -777,6 +778,10 @@ the "legacy" protocol needed by UxPlay.
ChangeLog
=========
1.52 2022-05-05 Cleaned up initial audio sync code, and reformatted
streaming debug output (readable aligned timestamps with decimal points
in seconds). Eliminate memory leaks (found by valgrind).
1.51 2022-04-24 Reworked options forVideo4Linux2 support (new option
-v4l2) and short options -rpi, -rpifb, -rpiwl as synonyms for -v4l2,
-v4l2 -vs kmssink, and -v4l2 -vs waylandsink. Reverted a change from

View File

@@ -186,6 +186,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
plist_to_xml(req_root_node, &plist_xml, &plist_len);
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
free(plist_xml);
plist_free(req_root_node);
} else if (data_is_text) {
char *data_str = utils_data_to_text((char *) request_data, request_datalen);
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
@@ -278,6 +279,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
}
}
}
plist_free(req_root_node);
if (conn->raop->callbacks.conn_teardown) {
conn->raop->callbacks.conn_teardown(conn->raop->callbacks.cls, &teardown_96, &teardown_110);
}
@@ -334,6 +336,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
char * plist_xml;
uint32_t plist_len;
plist_to_xml(res_root_node, &plist_xml, &plist_len);
plist_free(res_root_node);
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
free(plist_xml);
} else if (data_is_text) {
@@ -362,9 +365,6 @@ conn_destroy(void *ptr) {
conn->raop->callbacks.conn_destroy(conn->raop->callbacks.cls);
}
if (conn->raop_ntp) {
raop_ntp_destroy(conn->raop_ntp);
}
if (conn->raop_rtp) {
/* This is done in case TEARDOWN was not called */
raop_rtp_destroy(conn->raop_rtp);
@@ -373,7 +373,10 @@ conn_destroy(void *ptr) {
/* This is done in case TEARDOWN was not called */
raop_rtp_mirror_destroy(conn->raop_rtp_mirror);
}
if (conn->raop_ntp) {
raop_ntp_destroy(conn->raop_ntp);
}
if (conn->raop->callbacks.video_flush) {
conn->raop->callbacks.video_flush(conn->raop->callbacks.cls);
}

View File

@@ -28,6 +28,7 @@
#include "stream.h"
#include "global.h"
#include "utils.h"
#include "byteutils.h"
#define RAOP_BUFFER_LENGTH 32
@@ -221,7 +222,7 @@ raop_buffer_enqueue(raop_buffer_t *raop_buffer, unsigned char *data, unsigned sh
/* Get correct seqnum for the packet */
unsigned short seqnum;
if (use_seqnum) {
seqnum = (data[2] << 8) | data[3];
seqnum = byteutils_get_short_be(data, 2);
} else {
seqnum = raop_buffer->first_seqnum;
}
@@ -335,6 +336,7 @@ void raop_buffer_flush(raop_buffer_t *raop_buffer, int next_seq) {
for (int i = 0; i < RAOP_BUFFER_LENGTH; i++) {
if (raop_buffer->entries[i].payload_data) {
free(raop_buffer->entries[i].payload_data);
raop_buffer->entries[i].payload_data = NULL;
raop_buffer->entries[i].payload_size = 0;
}
raop_buffer->entries[i].filled = 0;

View File

@@ -161,6 +161,7 @@ raop_handler_info(raop_conn_t *conn,
plist_dict_set_item(r_node, "displays", displays_node);
plist_to_bin(r_node, response_data, (uint32_t *) response_datalen);
plist_free(r_node);
http_response_add_header(response, "Content-Type", "application/x-apple-binary-plist");
free(pk);
free(hw_addr);
@@ -349,6 +350,7 @@ raop_handler_setup(raop_conn_t *conn,
unsigned char aesiv[16];
unsigned char aeskey[16];
unsigned char eaeskey[72];
logger_log(conn->raop->logger, LOGGER_DEBUG, "SETUP 1");
@@ -357,6 +359,7 @@ raop_handler_setup(raop_conn_t *conn,
uint64_t eiv_len = 0;
plist_get_data_val(req_eiv_node, &eiv, &eiv_len);
memcpy(aesiv, eiv, 16);
free(eiv);
logger_log(conn->raop->logger, LOGGER_DEBUG, "eiv_len = %llu", eiv_len);
char* str = utils_data_to_string(aesiv, 16, 16);
logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aesiv (needed for AES-CBC audio decryption iv):\n%s", str);
@@ -365,13 +368,15 @@ raop_handler_setup(raop_conn_t *conn,
char* ekey = NULL;
uint64_t ekey_len = 0;
plist_get_data_val(req_ekey_node, &ekey, &ekey_len);
memcpy(eaeskey,ekey,72);
free(ekey);
logger_log(conn->raop->logger, LOGGER_DEBUG, "ekey_len = %llu", ekey_len);
// ekey is 72 bytes, aeskey is 16 bytes
str = utils_data_to_string((unsigned char *) ekey, ekey_len, 16);
// eaeskey is 72 bytes, aeskey is 16 bytes
str = utils_data_to_string((unsigned char *) eaeskey, ekey_len, 16);
logger_log(conn->raop->logger, LOGGER_DEBUG, "ekey:\n%s", str);
free (str);
int ret = fairplay_decrypt(conn->fairplay, (unsigned char*) ekey, aeskey);
int ret = fairplay_decrypt(conn->fairplay, (unsigned char*) eaeskey, aeskey);
logger_log(conn->raop->logger, LOGGER_DEBUG, "fairplay_decrypt ret = %d", ret);
str = utils_data_to_string(aeskey, 16, 16);
logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aeskey (fairplay-decrypted from ekey):\n%s", str);
@@ -393,7 +398,6 @@ raop_handler_setup(raop_conn_t *conn,
if (old_protocol) { /* some windows AirPlay-client emulators use old AirPlay 1 protocol with unhashed AES key */
logger_log(conn->raop->logger, LOGGER_INFO, "Client identifed as using old protocol (unhashed) AES audio key)");
} else {
unsigned char eaeskey[64] = {};
memcpy(eaeskey, aeskey, 16);
sha_ctx_t *ctx = sha_init();
sha_update(ctx, eaeskey, 16);
@@ -545,6 +549,8 @@ raop_handler_setup(raop_conn_t *conn,
}
plist_to_bin(res_root_node, response_data, (uint32_t*) response_datalen);
plist_free(res_root_node);
plist_free(req_root_node);
http_response_add_header(response, "Content-Type", "application/x-apple-binary-plist");
}

View File

@@ -167,6 +167,11 @@ raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp, co
raop_rtp->sync_data[i].rtp_epoch = 0;
}
raop_rtp->dacp_id = NULL;
raop_rtp->active_remote_header = NULL;
raop_rtp->metadata = NULL;
raop_rtp->coverart = NULL;
memcpy(&raop_rtp->callbacks, callbacks, sizeof(raop_callbacks_t));
raop_rtp->buffer = raop_buffer_init(logger, aeskey, aesiv);
if (!raop_rtp->buffer) {
@@ -419,7 +424,6 @@ raop_rtp_thread_udp(void *arg)
unsigned int packetlen;
struct sockaddr_storage saddr;
socklen_t saddrlen;
int64_t ntp_start_time = 0;
bool have_synced = false;
bool no_data = true;
/* the 44.1 kHZ rtp_time epoch is about 27 hours */
@@ -430,6 +434,8 @@ raop_rtp_thread_udp(void *arg)
int64_t half_epoch = (int64_t) epoch_length/2;
uint32_t prev_rtp_timestamp = 0;
assert(raop_rtp);
int64_t ntp_start_time = (int64_t) raop_ntp_get_local_time(raop_rtp->ntp);
logger_log(raop_rtp->logger, LOGGER_DEBUG, "raop_rtp start_time = %8.6f (raop_rtp audio)", ((double) ntp_start_time) / SEC);
while(1) {
fd_set rfds;
struct timeval tv;
@@ -480,7 +486,7 @@ raop_rtp_thread_udp(void *arg)
int result = raop_buffer_enqueue(raop_rtp->buffer, packet + offset, packetlen - offset, rtp_timestamp, 1);
assert(result >= 0);
} else if (type_c == 0x54 && packetlen >= 20) {
/* packet[0] = 0x90 (first sync) or 0x80 (subsequent ones) *
/* packet[0] = 0x90 (first sync ?) or 0x80 (subsequent ones) *
* packet[1:3] = 0xd4, 0xd4 && ~0x80 = type 54 *
* packet[2:3] = 0x00 0x04 *
* packet[4:7] : sync_rtp big-endian uint32_t) *
@@ -491,23 +497,28 @@ raop_rtp_thread_udp(void *arg)
// The unit for the rtp clock is 1 / sample rate = 1 / 44100
uint32_t sync_rtp = byteutils_get_int_be(packet, 4);
have_synced = true;
if (have_synced == false) {
logger_log(raop_rtp->logger, LOGGER_DEBUG, "first audio rtp sync");
have_synced = true;
}
uint64_t sync_ntp_raw = byteutils_get_long_be(packet, 8);
uint64_t sync_ntp_remote = raop_ntp_timestamp_to_micro_seconds(sync_ntp_raw, true);
uint64_t sync_ntp_local = raop_ntp_convert_remote_time(raop_rtp->ntp, sync_ntp_remote);
int64_t shift;
switch (raop_rtp->ct) {
case 0x08: /*AAC-ELD */
case 0x08: /*AAC-ELD */
shift = -11025; /* 44100/4 */
break;
case 0x02:
default:
shift = 0; /* not needed for ALAC (audio only) */
break;
}
raop_rtp_sync_clock(raop_rtp, sync_ntp_local, ntp_start_time, sync_rtp, shift);
char *str = utils_data_to_string(packet, packetlen, 20);
logger_log(raop_rtp->logger, LOGGER_DEBUG,
"raop_rtp sync: client ntp=%llu, local ntp: %llu, local ntp_start_time %lld, sync_rtp=%u\n%s",
sync_ntp_remote, sync_ntp_local, ntp_start_time, sync_rtp, str);
"raop_rtp sync: client ntp=%8.6f, ntp = %8.6f, ntp_start_time %8.6f, sync_rtp=%u\n%s",
((double) sync_ntp_remote) / SEC, ((double)sync_ntp_local) / SEC, ((double)ntp_start_time) / SEC, sync_rtp, str);
free(str);
} else {
logger_log(raop_rtp->logger, LOGGER_DEBUG, "raop_rtp unknown packet");
@@ -551,21 +562,35 @@ raop_rtp_thread_udp(void *arg)
} else {
if (no_data) {
no_data = false;
logger_log(raop_rtp->logger, LOGGER_DEBUG, "First audio packet received");
if (have_synced == false) {
/* until the first rpt sync occurs, we don't know the exact client ntp timestamp that matches the client rtp timesamp */
int64_t ntp_time = ((uint64_t) raop_ntp_get_local_time(raop_rtp->ntp)) - ntp_start_time;
/* until the first rtp sync occurs, we don't know the exact client ntp timestamp that matches the client rtp timesamp */
int64_t ntp_now = (int64_t) raop_ntp_get_local_time(raop_rtp->ntp);
int64_t ntp_time = ((int64_t) ntp_now) - ntp_start_time;
int64_t latency = 0;
switch (raop_rtp->ct) {
case 0x02:
ntp_time += DELAY_ALAC; /* DELAY = 2000000 (2.0 sec) is empirical choice for ALAC */
latency = -DELAY_ALAC; /* DELAY = 2000000 (2.0 sec) is empirical choice for ALAC */
break;
case 0x08:
ntp_time += DELAY_AAC; /* DELAY = 500000 (0.5 sec) is empirical choice for AAC-ELD */
latency = -DELAY_AAC; /* DELAY = 500000 (0.5 sec) is empirical choice for AAC-ELD */
break;
default:
break;
}
raop_rtp->rtp_sync_offset = ((int64_t) (((double) rtp_timestamp) / raop_rtp->rtp_sync_scale)) - ntp_time;
ntp_time -= latency;
logger_log(raop_rtp->logger, LOGGER_DEBUG, "First audio packet received, have_synced = false, using assumed latency %8.6f",
((double)latency) / SEC);
raop_rtp->rtp_sync_offset = (int64_t) ((double) rtp_timestamp) / raop_rtp->rtp_sync_scale;
raop_rtp->rtp_sync_offset -= ntp_time;
int64_t ntp_timestamp = ntp_start_time - raop_rtp->rtp_sync_offset;
ntp_timestamp += (int64_t) ((double) rtp_timestamp) / raop_rtp->rtp_sync_scale;
latency = ntp_now - ntp_timestamp;
unsigned short seqnum = byteutils_get_short_be(packet, 2);
logger_log(raop_rtp->logger, LOGGER_DEBUG,
"initial audio: now = %8.6f, npt = %8.6f, latency = %8.6f, rtp_time=%u seqnum = %u (not from sync)",
((double) ntp_now ) / SEC, ((double) ntp_timestamp) / SEC, ((double) latency) / SEC, rtp_timestamp, seqnum);
} else {
logger_log(raop_rtp->logger, LOGGER_DEBUG, "First audio packet received, have_synced = true");
}
}
int result = raop_buffer_enqueue(raop_rtp->buffer, packet, packetlen, rtp_timestamp, 1);
@@ -595,7 +620,9 @@ raop_rtp_thread_udp(void *arg)
audio_decode_struct audio_data;
audio_data.data_len = payload_size;
audio_data.data = payload;
audio_data.pts = (uint64_t) ntp_timestamp;
audio_data.ntp_time = (uint64_t) ntp_timestamp;
audio_data.rtp_time = (uint64_t) rtp_time;
audio_data.have_synced = have_synced;
raop_rtp->callbacks.audio_process(raop_rtp->callbacks.cls, raop_rtp->ntp, &audio_data);
free(payload);
uint64_t ntp_now = raop_ntp_get_local_time(raop_rtp->ntp);
@@ -737,7 +764,13 @@ raop_rtp_remote_control_id(raop_rtp_t *raop_rtp, const char *dacp_id, const char
/* Set dacp stuff in thread instead */
MUTEX_LOCK(raop_rtp->run_mutex);
if (raop_rtp->dacp_id) {
free(raop_rtp->dacp_id);
}
raop_rtp->dacp_id = strdup(dacp_id);
if (raop_rtp->active_remote_header) {
free(raop_rtp->active_remote_header);
}
raop_rtp->active_remote_header = strdup(active_remote_header);
MUTEX_UNLOCK(raop_rtp->run_mutex);
}

View File

@@ -356,6 +356,17 @@ raop_rtp_mirror_thread(void *arg)
if (!raop_rtp_mirror->sps_pps_waiting && packet[5] != 0x00) {
logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "unexpected: packet[5] = %2.2x, but not preceded by SPS+PPS packet", packet[5]);
}
/* if a previous unencrypted packet contains an SPS (type 7) and PPS (type 8) NAL which has not
* yet been sent, it should be prepended to the current NAL. In this case packet[5] is usually
* 0x10; however, the M1 Macs have increased the h264 level, and now the encrypted packet after the
* unencrypted SPS+PPS packet may contain a SEI (type 6) NAL prepended to the next VCL NAL, with
* packet[5] = 0x00. Now the flag raop_rtp_mirror->sps_pps_waiting = true will signal that a
* previous packet contained a SPS NAL + a PPS NAL, that has not yet been sent. This will trigger
* prepending it to the current NAL, and the sps_pps_waiting flag will be set to false after
* it has been prepended. It is not clear if the case packet[5] = 0x10 will occur when
* raop_rtp_mirror->sps_pps = false, but if it does, the current code will prepend the stored
* PPS + SPS NAL to the current encrypted NAL, and issue a warning message */
bool prepend_sps_pps = (raop_rtp_mirror->sps_pps_waiting || packet[5] != 0x00);
if (prepend_sps_pps) {
assert(raop_rtp_mirror->sps_pps);
@@ -364,10 +375,6 @@ raop_rtp_mirror_thread(void *arg)
memcpy(payload_out, raop_rtp_mirror->sps_pps, raop_rtp_mirror->sps_pps_len);
raop_rtp_mirror->sps_pps_waiting = false;
} else {
if (packet[5] != 0x00) {
logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "Warning: regular NAL unit, but packet[5] = 0x%2.2x is not 0x00", packet[5]);
}
assert(packet[5] == 0);
payload_out = (unsigned char*) malloc(payload_size);
payload_decrypted = payload_out;
}

View File

@@ -16,6 +16,7 @@
#define AIRPLAYSERVER_STREAM_H
#include <stdint.h>
#include <stdbool.h>
typedef struct {
int nal_count;
@@ -27,7 +28,9 @@ typedef struct {
typedef struct {
unsigned char *data;
int data_len;
uint64_t pts;
uint64_t ntp_time;
uint64_t rtp_time;
bool have_synced;
} audio_decode_struct;
#endif //AIRPLAYSERVER_STREAM_H

View File

@@ -26,12 +26,14 @@ extern "C" {
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include "../lib/raop_ntp.h"
void audio_renderer_init(logger_t *logger, const char* audiosink);
void audio_renderer_start(unsigned char* compression_type);
void audio_renderer_stop();
void audio_renderer_render_buffer(raop_ntp_t *ntp, unsigned char* data, int data_len, uint64_t pts);
void audio_renderer_render_buffer(raop_ntp_t *ntp, unsigned char* data, int data_len,
uint64_t ntp_time, uint64_t rtp_time, bool have_synced);
void audio_renderer_set_volume(float volume);
void audio_renderer_flush();
void audio_renderer_destroy();

View File

@@ -104,12 +104,13 @@ void audio_renderer_init(logger_t *render_logger, const char* audiosink) {
g_string_append (launch, audiosink);
g_string_append (launch, " sync=false");
renderer_type[i]->pipeline = gst_parse_launch(launch->str, &error);
if (error) {
g_error ("get_parse_launch error:\n %s\n",error->message);
if (error) {
g_error ("gst_parse_launch error (audio %d):\n %s\n", i+1, error->message);
g_clear_error (&error);
}
g_assert (renderer_type[i]->pipeline);
g_string_free(launch, TRUE);
g_assert (renderer_type[i]->pipeline);
renderer_type[i]->appsrc = gst_bin_get_by_name (GST_BIN (renderer_type[i]->pipeline), "audio_source");
renderer_type[i]->volume = gst_bin_get_by_name (GST_BIN (renderer_type[i]->pipeline), "volume");
switch (i) {
@@ -177,7 +178,8 @@ void audio_renderer_start(unsigned char *ct) {
}
void audio_renderer_render_buffer(raop_ntp_t *ntp, unsigned char* data, int data_len, uint64_t pts) {
void audio_renderer_render_buffer(raop_ntp_t *ntp, unsigned char* data, int data_len, uint64_t ntp_time,
uint64_t rtp_time, bool rtp_and_ntp_have_synced) {
GstBuffer *buffer;
bool valid;
if (data_len == 0 || renderer == NULL) return;
@@ -191,7 +193,7 @@ void audio_renderer_render_buffer(raop_ntp_t *ntp, unsigned char* data, int data
buffer = gst_buffer_new_and_alloc(data_len);
assert(buffer != NULL);
GST_BUFFER_PTS(buffer) = (GstClockTime) pts;
GST_BUFFER_PTS(buffer) = (GstClockTime) ntp_time;
gst_buffer_fill(buffer, 0, data, data_len);
switch (renderer->ct){
case 8: /*AAC-ELD*/

View File

@@ -146,6 +146,10 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, vide
g_string_append(launch, " name=video_sink sync=false");
logger_log(logger, LOGGER_DEBUG, "GStreamer video pipeline will be:\n\"%s\"", launch->str);
renderer->pipeline = gst_parse_launch(launch->str, &error);
if (error) {
g_error ("get_parse_launch error (video) :\n %s\n",error->message);
g_clear_error (&error);
}
g_assert (renderer->pipeline);
g_string_free(launch, TRUE);
@@ -264,7 +268,16 @@ gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, gpoin
gboolean flushing;
gst_message_parse_error (message, &err, &debug);
logger_log(logger, LOGGER_INFO, "GStreamer error: %s", err->message);
g_error_free (err);
if (strstr(err->message,"Internal data stream error")) {
logger_log(logger, LOGGER_INFO,
"*** This is a generic GStreamer error that usually means that GStreamer\n"
"*** was unable to construct a working video pipeline. If you are using\n"
"*** the default autovideosink for automated selection of the videosink,\n "
"*** GStreamer may be trying to use non-functional hardware h264 video decoding.\n"
"*** Try using option -avdec to force software decoding or use -vs <videosink>\n"
"*** to select a videosink of your choice (see \"man uxplay\")");
}
g_error_free (err);
g_free (debug);
gst_app_src_end_of_stream (GST_APP_SRC(renderer->appsrc));
flushing = TRUE;

View File

@@ -1,11 +1,11 @@
.TH UXPLAY "1" "April 2022" "1.51" "User Commands"
.TH UXPLAY "1" "May 2022" "1.52" "User Commands"
.SH NAME
uxplay \- start AirPlay server
.SH SYNOPSIS
.B uxplay
[\fI\,-n name\/\fR] [\fI\,-s wxh\/\fR] [\fI\,-p \/\fR[\fI\,n\/\fR]] [more \fI OPTIONS \/\fR ...]
.SH DESCRIPTION
UxPlay 1.51: An open\-source AirPlay mirroring server based on RPiPlay
UxPlay 1.52: An open\-source AirPlay mirroring server based on RPiPlay
.SH OPTIONS
.TP
.B

View File

@@ -44,7 +44,7 @@
#include "renderers/video_renderer.h"
#include "renderers/audio_renderer.h"
#define VERSION "1.51"
#define VERSION "1.52"
#define DEFAULT_NAME "UxPlay"
#define DEFAULT_DEBUG_LOG false
@@ -585,7 +585,7 @@ int main (int argc, char *argv[]) {
video_decoder = "avdec_h264";
video_converter.erase();
video_converter = "videoconvert";
} else if (arg == "-v4l2" || arg == "rpi") {
} else if (arg == "-v4l2" || arg == "-rpi") {
video_decoder.erase();
video_decoder = "v4l2h264dec";
video_converter.erase();
@@ -811,7 +811,7 @@ extern "C" void audio_process (void *cls, raop_ntp_t *ntp, audio_decode_struct *
dump_audio_to_file(data->data, data->data_len, (data->data)[0] & 0xf0);
}
if (use_audio) {
audio_renderer_render_buffer(ntp, data->data, data->data_len, data->pts);
audio_renderer_render_buffer(ntp, data->data, data->data_len, data->ntp_time, data->rtp_time, data->have_synced);
}
}