mirror of
https://github.com/morgan9e/UxPlay
synced 2026-04-14 00:04:13 +09:00
53
README.html
53
README.html
@@ -1,6 +1,6 @@
|
||||
<h1
|
||||
id="uxplay-1.62-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay
|
||||
1.62: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix
|
||||
id="uxplay-1.63-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay
|
||||
1.63: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix
|
||||
(now also runs on Windows).</h1>
|
||||
<h3
|
||||
id="now-developed-at-the-github-site-httpsgithub.comfdh2uxplay-where-all-user-issues-should-be-posted.">Now
|
||||
@@ -394,23 +394,21 @@ client drops the connection; since UxPlay-1.58, the option
|
||||
<code>-nohold</code> modifies this behavior so that when a new client
|
||||
requests a connection, it removes the current client and takes
|
||||
over.</p></li>
|
||||
<li><p>In Audio-Only mode, use the <code>-sync</code> option to
|
||||
synchronize audio on the server with video on the client. This
|
||||
introduces a delay that the client adds to account for latency. There is
|
||||
an option <code>-al x</code> that sets the audio latency <em>x</em> that
|
||||
the server reports to the client that in principle might modify the
|
||||
delay a little (this is not clear). Here (non-negative decimal)
|
||||
<em>x</em> is given in seconds, default is 0.25. <em>Without the
|
||||
<code>-sync</code> option, there is no audio delay, but the client’s
|
||||
video lags behind the server’s audio.</em></p></li>
|
||||
<li><p>Since UxPlay-1.54, you can display the accompanying “Cover Art”
|
||||
from sources like Apple Music in Audio-Only (ALAC) mode: run
|
||||
“<code>uxplay -ca <name> &</code>” in the background, then run
|
||||
a image viewer with an autoreload feature: an example is “feh”: run
|
||||
“<code>feh -R 1 <name></code>” in the foreground; terminate feh
|
||||
and then Uxplay with “<code>ctrl-C fg ctrl-C</code>”.</p></li>
|
||||
<li><p>If you wish to listen in Audio-Only mode on the server while
|
||||
watching the client screen (for video or Apple Music song lyrics, etc.),
|
||||
the video on the client is delayed by about 5 seconds behind the the
|
||||
audio on the server (<em>this is a Legacy mode issue: the client does
|
||||
not receive latency information to sync its video with audio played on
|
||||
the server</em>). Since UxPlay-1.62, this can be corrected with the
|
||||
<strong>audio offset</strong> option <code>-ao x</code> with an
|
||||
<em>x</em> of about 5.0 (allowed values are decimal numbers between 0
|
||||
and 10.0 seconds); this workaround just delays playing of audio on the
|
||||
server by <em>x</em> seconds, so the effect of pausing or changing
|
||||
tracks on the client will also be delayed.</p></li>
|
||||
</ul>
|
||||
<p><strong>One common problem involves GStreamer attempting to use
|
||||
incorrectly-configured or absent accelerated hardware h264 video
|
||||
@@ -691,6 +689,12 @@ the mirror display (X11) window.</p>
|
||||
<p><strong>-nh</strong> Do not append “<span class="citation"
|
||||
data-cites="_hostname_">@_hostname_</span>” at the end of the AirPlay
|
||||
server name.</p>
|
||||
<p><strong>-sync</strong> (In Audio-Only (ALAC)) mode: this option
|
||||
synchronizes audio on the server with video on the client, but causes
|
||||
the client to add a delay to account for latency, so pausing the stream
|
||||
will not take effect immediately. This can be mitigated by using the
|
||||
<code>-al</code> audio latency setting to change the latency (default
|
||||
0.25 secs) that the server reports to the cient.</p>
|
||||
<p><strong>-s wxh</strong> (e.g. -s 1920x1080 , which is the default )
|
||||
sets the display resolution (width and height, in pixels). (This may be
|
||||
a request made to the AirPlay client, and perhaps will not be the final
|
||||
@@ -786,17 +790,12 @@ parameters to be included with the audiosink name. (Some choices of
|
||||
audiosink might not work on your system.)</p>
|
||||
<p><strong>-as 0</strong> (or just <strong>-a</strong>) suppresses
|
||||
playing of streamed audio, but displays streamed video.</p>
|
||||
<p><strong>-ao x.y</strong> adds an audio offset time in (decimal)
|
||||
seconds to Audio-only (ALAC) streams to allow synchronization of sound
|
||||
playing on the UxPlay server with video on the client which delays
|
||||
playing the audio by <em>x.y</em> seconds (a decimal number). In the
|
||||
AirPlay Legacy mode used by UxPlay, the client cannot obtain audio
|
||||
latency information from the server, and appears to assume a latency of
|
||||
about 5 seconds. This can be compensated for with offset values such as
|
||||
<code>-ao 5</code> (but the effect of a pause in play etc., on the
|
||||
client will also be delayed). The -ao option accepts values in the range
|
||||
[0,10], which it converts to a whole number of milliseconds (-ao 1.2345
|
||||
gives 1234 msec audio delay).</p>
|
||||
<p><strong>-al <em>x</em></strong> specifies an audio latency <em>x</em>
|
||||
in (decimal) seconds in Audio-only (ALAC), that is reported to the
|
||||
client. Values in the range [0.0, 10.0] seconds are allowed, and will be
|
||||
converted to a whole number of microseconds. Default is 0.25 sec (250000
|
||||
usec). (This replaces the <code>-ao</code> option introduced in v1.62,
|
||||
as a workaround for a problem that is now fixed).</p>
|
||||
<p><strong>-ca <em>filename</em></strong> provides a file (where
|
||||
<em>filename</em> can include a full path) used for output of “cover
|
||||
art” (from Apple Music, <em>etc.</em>,) in audio-only ALAC mode. This
|
||||
@@ -1140,6 +1139,12 @@ as “SupportsLegacyPairing”) of the “features” plist code (reported to
|
||||
the client by the AirPlay server) to be set. The “features” code and
|
||||
other settings are set in <code>UxPlay/lib/dnssdint.h</code>.</p>
|
||||
<h1 id="changelog">Changelog</h1>
|
||||
<p>1.63 2023-02-06 Corrected -ao option. Now allows audio latency
|
||||
reported to client to be changed from default of 0.25 sec (may not be
|
||||
necessary). Synchronisation of audio on server with video on client in
|
||||
audio-only ALAC mode now works, as sync=true is now used in the ALAC
|
||||
GStreamer pipeline. Internal change: all times are now given in
|
||||
nanoseconds.</p>
|
||||
<p>1.62 2023-01-18 Added Audio-only mode time offset -ao x to allow user
|
||||
synchronization of ALAC audio playing on the server with video, song
|
||||
lyrics, etc. playing on the client. x = 5.0 appears to be optimal in
|
||||
|
||||
36
README.md
36
README.md
@@ -1,4 +1,4 @@
|
||||
# UxPlay 1.62: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
|
||||
# UxPlay 1.63: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
|
||||
|
||||
### Now developed at the GitHub site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where all user issues should be posted).
|
||||
|
||||
@@ -333,18 +333,17 @@ per second. (You can see what framerate is actually streaming by using -vs fpsdi
|
||||
its current client until that client drops the connection; since UxPlay-1.58, the option `-nohold` modifies this
|
||||
behavior so that when a new client requests a connection, it removes the current client and takes over.
|
||||
|
||||
* In Audio-Only mode, use the `-sync` option to synchronize audio on the server with video
|
||||
on the client. This introduces a delay that the client adds to account for latency. There is an
|
||||
option `-al x` that sets the audio latency _x_ that the server reports to the client that in principle
|
||||
might modify the delay a little (this is not clear). Here (non-negative decimal) _x_ is given in seconds, default
|
||||
is 0.25. _Without the `-sync` option, there is no audio delay, but the client's video lags behind the server's audio._
|
||||
|
||||
* Since UxPlay-1.54, you can display the accompanying "Cover Art" from sources like Apple Music in Audio-Only (ALAC) mode:
|
||||
run "`uxplay -ca <name> &`" in the background, then run a image viewer with an autoreload feature: an example
|
||||
is "feh": run "``feh -R 1 <name>``"
|
||||
in the foreground; terminate feh and then Uxplay with "`ctrl-C fg ctrl-C`".
|
||||
|
||||
* If you wish to listen in Audio-Only mode on the server while watching the client screen (for video or Apple Music song lyrics, etc.),
|
||||
the video on the client is delayed by about 5 seconds behind the the audio on the server (_this is a Legacy mode issue: the client
|
||||
does not receive latency information to sync its video with audio played on the server_). Since UxPlay-1.62, this can be corrected with
|
||||
the **audio offset** option `-ao x` with an _x_ of about 5.0
|
||||
(allowed values are decimal numbers between 0 and 10.0 seconds); this workaround just delays playing of audio on the server by _x_ seconds, so the effect
|
||||
of pausing or changing tracks on the client will also be delayed.
|
||||
|
||||
**One common problem involves GStreamer
|
||||
attempting to use incorrectly-configured or absent accelerated hardware h264
|
||||
video decoding (e.g., VAAPI).
|
||||
@@ -573,6 +572,12 @@ Options:
|
||||
|
||||
**-nh** Do not append "@_hostname_" at the end of the AirPlay server name.
|
||||
|
||||
**-sync** (In Audio-Only (ALAC)) mode: this option synchronizes audio on the server with video on the client,
|
||||
but causes the client to add a delay to account for latency, so pausing the stream will not take effect
|
||||
immediately. This can be mitigated by using the `-al` audio latency setting to change the latency (default 0.25 secs)
|
||||
that the server reports to the cient.
|
||||
|
||||
|
||||
**-s wxh** (e.g. -s 1920x1080 , which is the default ) sets the display resolution (width and height,
|
||||
in pixels). (This may be a
|
||||
request made to the AirPlay client, and perhaps will not
|
||||
@@ -664,12 +669,10 @@ which will not work if a firewall is running.
|
||||
|
||||
**-as 0** (or just **-a**) suppresses playing of streamed audio, but displays streamed video.
|
||||
|
||||
**-ao x.y** adds an audio offset time in (decimal) seconds to Audio-only (ALAC) streams to allow synchronization of sound
|
||||
playing on the UxPlay server with video on the client which delays playing the audio by _x.y_ seconds (a
|
||||
decimal number). In the AirPlay Legacy mode used by UxPlay, the client cannot obtain audio latency information
|
||||
from the server, and appears to assume a latency of about 5 seconds. This can be compensated for with offset values such
|
||||
as `-ao 5` (but the effect of a pause in play etc., on the client will also be delayed). The -ao option accepts
|
||||
values in the range [0,10], which it converts to a whole number of milliseconds (-ao 1.2345 gives 1234 msec audio delay).
|
||||
**-al _x_** specifies an audio latency _x_ in (decimal) seconds in Audio-only (ALAC), that is reported to the client. Values
|
||||
in the range [0.0, 10.0] seconds are allowed, and will be converted to a whole number of microseconds. Default
|
||||
is 0.25 sec (250000 usec). (This replaces the `-ao` option introduced in v1.62, as a workaround for a problem that
|
||||
is now fixed).
|
||||
|
||||
**-ca _filename_** provides a file (where _filename_ can include a full path) used for output of "cover art"
|
||||
(from Apple Music, _etc._,) in audio-only ALAC mode. This file is overwritten with the latest cover art as
|
||||
@@ -945,6 +948,11 @@ tvOS 12.2.1); it seems that the use of "legacy" protocol just requires bit 27 (l
|
||||
The "features" code and other settings are set in `UxPlay/lib/dnssdint.h`.
|
||||
|
||||
# Changelog
|
||||
1.63 2023-02-06 Corrected -ao option. Now allows audio latency reported to client to be changed
|
||||
from default of 0.25 sec (may not be necessary). Synchronisation of audio on server
|
||||
with video on client in audio-only ALAC mode now works, as sync=true is now used in
|
||||
the ALAC GStreamer pipeline. Internal change: all times are now given in nanoseconds.
|
||||
|
||||
1.62 2023-01-18 Added Audio-only mode time offset -ao x to allow user synchronization of ALAC
|
||||
audio playing on the server with video, song lyrics, etc. playing on the client.
|
||||
x = 5.0 appears to be optimal in many cases. Quality fixes: cleanup in volume
|
||||
|
||||
52
README.txt
52
README.txt
@@ -1,4 +1,4 @@
|
||||
# UxPlay 1.62: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
|
||||
# UxPlay 1.63: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
|
||||
|
||||
### Now developed at the GitHub site <https://github.com/FDH2/UxPlay> (where all user issues should be posted).
|
||||
|
||||
@@ -397,6 +397,15 @@ for help with this or other problems.
|
||||
modifies this behavior so that when a new client requests a
|
||||
connection, it removes the current client and takes over.
|
||||
|
||||
- In Audio-Only mode, use the `-sync` option to synchronize audio on
|
||||
the server with video on the client. This introduces a delay that
|
||||
the client adds to account for latency. There is an option `-al x`
|
||||
that sets the audio latency *x* that the server reports to the
|
||||
client that in principle might modify the delay a little (this is
|
||||
not clear). Here (non-negative decimal) *x* is given in seconds,
|
||||
default is 0.25. *Without the `-sync` option, there is no audio
|
||||
delay, but the client's video lags behind the server's audio.*
|
||||
|
||||
- Since UxPlay-1.54, you can display the accompanying "Cover Art" from
|
||||
sources like Apple Music in Audio-Only (ALAC) mode: run
|
||||
"`uxplay -ca <name> &`" in the background, then run a image viewer
|
||||
@@ -404,18 +413,6 @@ for help with this or other problems.
|
||||
"`feh -R 1 <name>`" in the foreground; terminate feh and then Uxplay
|
||||
with "`ctrl-C fg ctrl-C`".
|
||||
|
||||
- If you wish to listen in Audio-Only mode on the server while
|
||||
watching the client screen (for video or Apple Music song lyrics,
|
||||
etc.), the video on the client is delayed by about 5 seconds behind
|
||||
the the audio on the server (*this is a Legacy mode issue: the
|
||||
client does not receive latency information to sync its video with
|
||||
audio played on the server*). Since UxPlay-1.62, this can be
|
||||
corrected with the **audio offset** option `-ao x` with an *x* of
|
||||
about 5.0 (allowed values are decimal numbers between 0 and 10.0
|
||||
seconds); this workaround just delays playing of audio on the server
|
||||
by *x* seconds, so the effect of pausing or changing tracks on the
|
||||
client will also be delayed.
|
||||
|
||||
**One common problem involves GStreamer attempting to use
|
||||
incorrectly-configured or absent accelerated hardware h264 video
|
||||
decoding (e.g., VAAPI). Try "`uxplay -avdec`" to force software video
|
||||
@@ -703,6 +700,13 @@ will also now be the name shown above the mirror display (X11) window.
|
||||
**-nh** Do not append "@_hostname_" at the end of the AirPlay server
|
||||
name.
|
||||
|
||||
**-sync** (In Audio-Only (ALAC)) mode: this option synchronizes audio on
|
||||
the server with video on the client, but causes the client to add a
|
||||
delay to account for latency, so pausing the stream will not take effect
|
||||
immediately. This can be mitigated by using the `-al` audio latency
|
||||
setting to change the latency (default 0.25 secs) that the server
|
||||
reports to the cient.
|
||||
|
||||
**-s wxh** (e.g. -s 1920x1080 , which is the default ) sets the display
|
||||
resolution (width and height, in pixels). (This may be a request made to
|
||||
the AirPlay client, and perhaps will not be the final resolution you
|
||||
@@ -808,16 +812,12 @@ name. (Some choices of audiosink might not work on your system.)
|
||||
**-as 0** (or just **-a**) suppresses playing of streamed audio, but
|
||||
displays streamed video.
|
||||
|
||||
**-ao x.y** adds an audio offset time in (decimal) seconds to Audio-only
|
||||
(ALAC) streams to allow synchronization of sound playing on the UxPlay
|
||||
server with video on the client which delays playing the audio by *x.y*
|
||||
seconds (a decimal number). In the AirPlay Legacy mode used by UxPlay,
|
||||
the client cannot obtain audio latency information from the server, and
|
||||
appears to assume a latency of about 5 seconds. This can be compensated
|
||||
for with offset values such as `-ao 5` (but the effect of a pause in
|
||||
play etc., on the client will also be delayed). The -ao option accepts
|
||||
values in the range \[0,10\], which it converts to a whole number of
|
||||
milliseconds (-ao 1.2345 gives 1234 msec audio delay).
|
||||
**-al *x*** specifies an audio latency *x* in (decimal) seconds in
|
||||
Audio-only (ALAC), that is reported to the client. Values in the range
|
||||
\[0.0, 10.0\] seconds are allowed, and will be converted to a whole
|
||||
number of microseconds. Default is 0.25 sec (250000 usec). (This
|
||||
replaces the `-ao` option introduced in v1.62, as a workaround for a
|
||||
problem that is now fixed).
|
||||
|
||||
**-ca *filename*** provides a file (where *filename* can include a full
|
||||
path) used for output of "cover art" (from Apple Music, *etc.*,) in
|
||||
@@ -1183,6 +1183,12 @@ other settings are set in `UxPlay/lib/dnssdint.h`.
|
||||
|
||||
# Changelog
|
||||
|
||||
1.63 2023-02-06 Corrected -ao option. Now allows audio latency reported
|
||||
to client to be changed from default of 0.25 sec (may not be necessary).
|
||||
Synchronisation of audio on server with video on client in audio-only
|
||||
ALAC mode now works, as sync=true is now used in the ALAC GStreamer
|
||||
pipeline. Internal change: all times are now given in nanoseconds.
|
||||
|
||||
1.62 2023-01-18 Added Audio-only mode time offset -ao x to allow user
|
||||
synchronization of ALAC audio playing on the server with video, song
|
||||
lyrics, etc. playing on the client. x = 5.0 appears to be optimal in
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
* Lesser General Public License for more details.
|
||||
*/
|
||||
|
||||
#define SECOND_IN_NSECS 1000000000UL
|
||||
|
||||
#include <time.h>
|
||||
#ifdef _WIN32
|
||||
# include <winsock2.h>
|
||||
@@ -96,23 +98,23 @@ void byteutils_put_int(unsigned char* b, int offset, uint32_t value) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an ntp timestamp and returns it as micro seconds since the Unix epoch
|
||||
* Reads an ntp timestamp and returns it as nano seconds since the Unix epoch
|
||||
*/
|
||||
uint64_t byteutils_get_ntp_timestamp(unsigned char *b, int offset) {
|
||||
uint64_t seconds = ntohl(((unsigned int) byteutils_get_int(b, offset))) - SECONDS_FROM_1900_TO_1970;
|
||||
uint64_t fraction = ntohl((unsigned int) byteutils_get_int(b, offset + 4));
|
||||
return (seconds * 1000000L) + ((fraction * 1000000L) >> 32);
|
||||
return (seconds * SECOND_IN_NSECS) + ((fraction * SECOND_IN_NSECS) >> 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a time given as micro seconds since the Unix time epoch as an ntp timestamp
|
||||
* Writes a time given as nano seconds since the Unix time epoch as an ntp timestamp
|
||||
* into the buffer at position offset
|
||||
*/
|
||||
void byteutils_put_ntp_timestamp(unsigned char *b, int offset, uint64_t us_since_1970) {
|
||||
uint64_t seconds = us_since_1970 / 1000000L;
|
||||
uint64_t microseconds = us_since_1970 % 1000000L;
|
||||
void byteutils_put_ntp_timestamp(unsigned char *b, int offset, uint64_t ns_since_1970) {
|
||||
uint64_t seconds = ns_since_1970 / SECOND_IN_NSECS;
|
||||
uint64_t nanoseconds = ns_since_1970 % SECOND_IN_NSECS;
|
||||
seconds += SECONDS_FROM_1900_TO_1970;
|
||||
uint64_t fraction = (microseconds << 32) / 1000000L;
|
||||
uint64_t fraction = (nanoseconds << 32) / SECOND_IN_NSECS;
|
||||
|
||||
// Write in big endian!
|
||||
byteutils_put_int(b, offset, htonl(seconds));
|
||||
|
||||
@@ -60,6 +60,7 @@ struct raop_s {
|
||||
uint8_t overscanned;
|
||||
uint8_t clientFPSdata;
|
||||
|
||||
int audio_delay_micros;
|
||||
int max_ntp_timeouts;
|
||||
};
|
||||
|
||||
@@ -461,6 +462,7 @@ raop_init(int max_clients, raop_callbacks_t *callbacks) {
|
||||
raop->clientFPSdata = 0;
|
||||
|
||||
raop->max_ntp_timeouts = 0;
|
||||
raop->audio_delay_micros = 250000;
|
||||
|
||||
return raop;
|
||||
}
|
||||
@@ -519,6 +521,11 @@ int raop_set_plist(raop_t *raop, const char *plist_item, const int value) {
|
||||
} else if (strcmp(plist_item, "max_ntp_timeouts") == 0) {
|
||||
raop->max_ntp_timeouts = (value > 0 ? value : 0);
|
||||
if (raop->max_ntp_timeouts != value) retval = 1;
|
||||
} else if (strcmp(plist_item, "audio_delay_micros") == 0) {
|
||||
if (value >= 0 && value <= 10 * SECOND_IN_USECS) {
|
||||
raop->audio_delay_micros = value;
|
||||
}
|
||||
if (raop->audio_delay_micros != value) retval = 1;
|
||||
} else {
|
||||
retval = -1;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ typedef struct {
|
||||
|
||||
/* RTP header */
|
||||
unsigned short seqnum;
|
||||
uint64_t timestamp;
|
||||
uint64_t rtp_timestamp;
|
||||
uint64_t ntp_timestamp;
|
||||
|
||||
/* Payload data */
|
||||
unsigned int payload_size;
|
||||
@@ -206,7 +207,7 @@ raop_buffer_decrypt(raop_buffer_t *raop_buffer, unsigned char *data, unsigned ch
|
||||
}
|
||||
|
||||
int
|
||||
raop_buffer_enqueue(raop_buffer_t *raop_buffer, unsigned char *data, unsigned short datalen, uint64_t timestamp, int use_seqnum) {
|
||||
raop_buffer_enqueue(raop_buffer_t *raop_buffer, unsigned char *data, unsigned short datalen, uint64_t *ntp_timestamp, uint64_t *rtp_timestamp, int use_seqnum) {
|
||||
unsigned char empty_packet_marker[] = { 0x00, 0x68, 0x34, 0x00 };
|
||||
assert(raop_buffer);
|
||||
|
||||
@@ -247,7 +248,8 @@ raop_buffer_enqueue(raop_buffer_t *raop_buffer, unsigned char *data, unsigned sh
|
||||
|
||||
/* Update the raop_buffer entry header */
|
||||
entry->seqnum = seqnum;
|
||||
entry->timestamp = timestamp;
|
||||
entry->rtp_timestamp = *rtp_timestamp;
|
||||
entry->ntp_timestamp = *ntp_timestamp;
|
||||
entry->filled = 1;
|
||||
|
||||
entry->payload_data = malloc(payload_size);
|
||||
@@ -268,7 +270,7 @@ raop_buffer_enqueue(raop_buffer_t *raop_buffer, unsigned char *data, unsigned sh
|
||||
}
|
||||
|
||||
void *
|
||||
raop_buffer_dequeue(raop_buffer_t *raop_buffer, unsigned int *length, uint64_t *timestamp, unsigned short *seqnum, int no_resend) {
|
||||
raop_buffer_dequeue(raop_buffer_t *raop_buffer, unsigned int *length, uint64_t *ntp_timestamp, uint64_t *rtp_timestamp, unsigned short *seqnum, int no_resend) {
|
||||
assert(raop_buffer);
|
||||
|
||||
/* Calculate number of entries in the current buffer */
|
||||
@@ -300,7 +302,8 @@ raop_buffer_dequeue(raop_buffer_t *raop_buffer, unsigned int *length, uint64_t *
|
||||
entry->filled = 0;
|
||||
|
||||
/* Return entry payload buffer */
|
||||
*timestamp = entry->timestamp;
|
||||
*rtp_timestamp = entry->rtp_timestamp;
|
||||
*ntp_timestamp = entry->ntp_timestamp;
|
||||
*seqnum = entry->seqnum;
|
||||
*length = entry->payload_size;
|
||||
entry->payload_size = 0;
|
||||
|
||||
@@ -25,8 +25,8 @@ typedef int (*raop_resend_cb_t)(void *opaque, unsigned short seqno, unsigned sho
|
||||
raop_buffer_t *raop_buffer_init(logger_t *logger,
|
||||
const unsigned char *aeskey,
|
||||
const unsigned char *aesiv);
|
||||
int raop_buffer_enqueue(raop_buffer_t *raop_buffer, unsigned char *data, unsigned short datalen, uint64_t timestamp, int use_seqnum);
|
||||
void *raop_buffer_dequeue(raop_buffer_t *raop_buffer, unsigned int *length, uint64_t *timestamp, unsigned short *seqnum, int no_resend);
|
||||
int raop_buffer_enqueue(raop_buffer_t *raop_buffer, unsigned char *data, unsigned short datalen, uint64_t *ntp_timestamp, uint64_t *rtp_timestamp, int use_seqnum);
|
||||
void *raop_buffer_dequeue(raop_buffer_t *raop_buffer, unsigned int *length, uint64_t *ntp_timestamp, uint64_t *rtp_timestamp, unsigned short *seqnum, int no_resend);
|
||||
void raop_buffer_handle_resends(raop_buffer_t *raop_buffer, raop_resend_cb_t resend_cb, void *opaque);
|
||||
void raop_buffer_flush(raop_buffer_t *raop_buffer, int next_seq);
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <plist/plist.h>
|
||||
#define AUDIO_SAMPLE_RATE 44100 /* all supported AirPlay audio format use this sample rate */
|
||||
#define SECOND_IN_USECS 1000000
|
||||
|
||||
typedef void (*raop_handler_t)(raop_conn_t *, http_request_t *,
|
||||
http_response_t *, char **, int *);
|
||||
@@ -500,6 +502,8 @@ raop_handler_setup(raop_conn_t *conn,
|
||||
unsigned short cport = conn->raop->control_lport, dport = conn->raop->data_lport;
|
||||
unsigned short remote_cport = 0;
|
||||
unsigned char ct;
|
||||
unsigned int sr = AUDIO_SAMPLE_RATE; /* all AirPlay audio formats supported so far have sample rate 44.1kHz */
|
||||
|
||||
uint64_t uint_val = 0;
|
||||
plist_t req_stream_control_port_node = plist_dict_get_item(req_stream_node, "controlPort");
|
||||
plist_get_uint_val(req_stream_control_port_node, &uint_val);
|
||||
@@ -544,7 +548,7 @@ raop_handler_setup(raop_conn_t *conn,
|
||||
}
|
||||
|
||||
if (conn->raop_rtp) {
|
||||
raop_rtp_start_audio(conn->raop_rtp, use_udp, remote_cport, &cport, &dport, ct);
|
||||
raop_rtp_start_audio(conn->raop_rtp, use_udp, &remote_cport, &cport, &dport, &ct, &sr);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "RAOP initialized success");
|
||||
} else {
|
||||
logger_log(conn->raop->logger, LOGGER_ERR, "RAOP not initialized at SETUP, playing will fail!");
|
||||
@@ -685,7 +689,10 @@ raop_handler_record(raop_conn_t *conn,
|
||||
http_request_t *request, http_response_t *response,
|
||||
char **response_data, int *response_datalen)
|
||||
{
|
||||
char audio_latency[12];
|
||||
unsigned int ad = (unsigned int) (((uint64_t) conn->raop->audio_delay_micros) * AUDIO_SAMPLE_RATE / SECOND_IN_USECS);
|
||||
sprintf(audio_latency, "%u", ad);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "raop_handler_record");
|
||||
http_response_add_header(response, "Audio-Latency", "11025");
|
||||
http_response_add_header(response, "Audio-Latency", audio_latency);
|
||||
http_response_add_header(response, "Audio-Jack-Status", "connected; type=analog");
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "byteutils.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define SECOND_IN_NSECS 1000000000UL
|
||||
#define RAOP_NTP_DATA_COUNT 8
|
||||
#define RAOP_NTP_PHI_PPM 15ull // PPM
|
||||
#define RAOP_NTP_R_RHO ((1ull << 32) / 1000u) // packet precision
|
||||
@@ -284,7 +285,10 @@ raop_ntp_thread(void *arg)
|
||||
byteutils_put_ntp_timestamp(request, 24, send_time);
|
||||
int send_len = sendto(raop_ntp->tsock, (char *)request, sizeof(request), 0,
|
||||
(struct sockaddr *) &raop_ntp->remote_saddr, raop_ntp->remote_saddr_len);
|
||||
logger_log(raop_ntp->logger, LOGGER_DEBUG, "\nraop_ntp send_len = %d, now = %llu", send_len, send_time);
|
||||
char *str = utils_data_to_string(request, send_len, 16);
|
||||
logger_log(raop_ntp->logger, LOGGER_DEBUG, "\nraop_ntp send time type_t=%d send_len = %d, now = %8.6f\n%s",
|
||||
request[1] &~0x80, send_len, (double) send_time / SECOND_IN_NSECS, str);
|
||||
free(str);
|
||||
if (send_len < 0) {
|
||||
logger_log(raop_ntp->logger, LOGGER_ERR, "raop_ntp error sending request");
|
||||
} else {
|
||||
@@ -305,12 +309,7 @@ raop_ntp_thread(void *arg)
|
||||
} else {
|
||||
//local time of the server when the NTP response packet returns
|
||||
int64_t t3 = (int64_t) raop_ntp_get_local_time(raop_ntp);
|
||||
|
||||
timeout_counter = 0;
|
||||
char *str = utils_data_to_string(response, response_len, 16);
|
||||
logger_log(raop_ntp->logger, LOGGER_DEBUG, "raop_ntp receive time type_t=%d packetlen = %d\n%s",
|
||||
response[1] &~0x80, response_len, str);
|
||||
free(str);
|
||||
|
||||
// Local time of the server when the NTP request packet leaves the server
|
||||
int64_t t0 = (int64_t) byteutils_get_ntp_timestamp(response, 8);
|
||||
@@ -321,15 +320,20 @@ raop_ntp_thread(void *arg)
|
||||
// Local time of the client when the response message leaves the client
|
||||
int64_t t2 = (int64_t) byteutils_get_ntp_timestamp(response, 24);
|
||||
|
||||
// The iOS client device sends its time in micro seconds relative to an arbitrary Epoch (the last boot).
|
||||
// For a little bonus confusion, they add SECONDS_FROM_1900_TO_1970 * 1000000 us.
|
||||
char *str = utils_data_to_string(response, response_len, 16);
|
||||
logger_log(raop_ntp->logger, LOGGER_DEBUG, "raop_ntp receive time type_t=%d packetlen = %d, now = %8.6f t1 = %8.6f, t2 = %8.6f\n%s",
|
||||
response[1] &~0x80, response_len, (double) t3 / SECOND_IN_NSECS, (double) t1 / SECOND_IN_NSECS, (double) t2 / SECOND_IN_NSECS, str);
|
||||
free(str);
|
||||
|
||||
// The iOS client device sends its time in seconds relative to an arbitrary Epoch (the last boot).
|
||||
// For a little bonus confusion, they add SECONDS_FROM_1900_TO_1970.
|
||||
// This means we have to expect some rather huge offset, but its growth or shrink over time should be small.
|
||||
|
||||
raop_ntp->data_index = (raop_ntp->data_index + 1) % RAOP_NTP_DATA_COUNT;
|
||||
raop_ntp->data[raop_ntp->data_index].time = t3;
|
||||
raop_ntp->data[raop_ntp->data_index].offset = ((t1 - t0) + (t2 - t3)) / 2;
|
||||
raop_ntp->data[raop_ntp->data_index].delay = ((t3 - t0) - (t2 - t1));
|
||||
raop_ntp->data[raop_ntp->data_index].dispersion = RAOP_NTP_R_RHO + RAOP_NTP_S_RHO + (t3 - t0) * RAOP_NTP_PHI_PPM / 1000000u;
|
||||
raop_ntp->data[raop_ntp->data_index].dispersion = RAOP_NTP_R_RHO + RAOP_NTP_S_RHO + (t3 - t0) * RAOP_NTP_PHI_PPM / SECOND_IN_NSECS;
|
||||
|
||||
// Sort by delay
|
||||
memcpy(data_sorted, raop_ntp->data, sizeof(data_sorted));
|
||||
@@ -341,7 +345,7 @@ raop_ntp_thread(void *arg)
|
||||
|
||||
// Calculate dispersion
|
||||
for(int i = 0; i < RAOP_NTP_DATA_COUNT; ++i) {
|
||||
unsigned long long disp = raop_ntp->data[i].dispersion + (t3 - raop_ntp->data[i].time) * RAOP_NTP_PHI_PPM / 1000000u;
|
||||
unsigned long long disp = raop_ntp->data[i].dispersion + (t3 - raop_ntp->data[i].time) * RAOP_NTP_PHI_PPM / SECOND_IN_NSECS;
|
||||
dispersion += disp / two_pow_n[i];
|
||||
}
|
||||
|
||||
@@ -454,54 +458,54 @@ raop_ntp_stop(raop_ntp_t *raop_ntp)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from a little endian ntp timestamp to micro seconds since the Unix epoch.
|
||||
* Converts from a little endian ntp timestamp to nano seconds since the Unix epoch.
|
||||
* Does the same thing as byteutils_get_ntp_timestamp, except its input is an uint64_t
|
||||
* and expected to already be in little endian.
|
||||
* Please note this just converts to a different representation, the clock remains the
|
||||
* same.
|
||||
*/
|
||||
uint64_t raop_ntp_timestamp_to_micro_seconds(uint64_t ntp_timestamp, bool account_for_epoch_diff) {
|
||||
uint64_t raop_ntp_timestamp_to_nano_seconds(uint64_t ntp_timestamp, bool account_for_epoch_diff) {
|
||||
uint64_t seconds = ((ntp_timestamp >> 32) & 0xffffffff) - (account_for_epoch_diff ? SECONDS_FROM_1900_TO_1970 : 0);
|
||||
uint64_t fraction = (ntp_timestamp & 0xffffffff);
|
||||
return (seconds * 1000000) + ((fraction * 1000000) >> 32);
|
||||
return (seconds * SECOND_IN_NSECS) + ((fraction * SECOND_IN_NSECS) >> 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current time in micro seconds according to the local wall clock.
|
||||
* Returns the current time in nano seconds according to the local wall clock.
|
||||
* The system Unix time is used as the local wall clock.
|
||||
*/
|
||||
uint64_t raop_ntp_get_local_time(raop_ntp_t *raop_ntp) {
|
||||
struct timespec time;
|
||||
clock_gettime(CLOCK_REALTIME, &time);
|
||||
return (uint64_t)time.tv_sec * 1000000L + (uint64_t)(time.tv_nsec / 1000);
|
||||
return ((uint64_t)time.tv_sec) * SECOND_IN_NSECS + (uint64_t)(time.tv_nsec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current time in micro seconds according to the remote wall clock.
|
||||
* Returns the current time in nano seconds according to the remote wall clock.
|
||||
*/
|
||||
uint64_t raop_ntp_get_remote_time(raop_ntp_t *raop_ntp) {
|
||||
MUTEX_LOCK(raop_ntp->sync_params_mutex);
|
||||
int64_t offset = raop_ntp->sync_offset;
|
||||
MUTEX_UNLOCK(raop_ntp->sync_params_mutex);
|
||||
return (uint64_t) ((int64_t) raop_ntp_get_local_time(raop_ntp)) + ((int64_t) offset);
|
||||
return (uint64_t) (((int64_t) raop_ntp_get_local_time(raop_ntp)) + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local wall clock time in micro seconds for the given point in remote clock time
|
||||
* Returns the local wall clock time in nano seconds for the given point in remote clock time
|
||||
*/
|
||||
uint64_t raop_ntp_convert_remote_time(raop_ntp_t *raop_ntp, uint64_t remote_time) {
|
||||
MUTEX_LOCK(raop_ntp->sync_params_mutex);
|
||||
uint64_t offset = raop_ntp->sync_offset;
|
||||
int64_t offset = raop_ntp->sync_offset;
|
||||
MUTEX_UNLOCK(raop_ntp->sync_params_mutex);
|
||||
return (uint64_t) ((int64_t) remote_time) - ((int64_t) offset);
|
||||
return (uint64_t) (((int64_t) remote_time) - offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remote wall clock time in micro seconds for the given point in local clock time
|
||||
* Returns the remote wall clock time in nano seconds for the given point in local clock time
|
||||
*/
|
||||
uint64_t raop_ntp_convert_local_time(raop_ntp_t *raop_ntp, uint64_t local_time) {
|
||||
MUTEX_LOCK(raop_ntp->sync_params_mutex);
|
||||
uint64_t offset = raop_ntp->sync_offset;
|
||||
int64_t offset = raop_ntp->sync_offset;
|
||||
MUTEX_UNLOCK(raop_ntp->sync_params_mutex);
|
||||
return (uint64_t) ((int64_t) local_time) + ((int64_t) offset);
|
||||
return (uint64_t) (((int64_t) local_time) + offset);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ unsigned short raop_ntp_get_port(raop_ntp_t *raop_ntp);
|
||||
|
||||
void raop_ntp_destroy(raop_ntp_t *raop_rtp);
|
||||
|
||||
uint64_t raop_ntp_timestamp_to_micro_seconds(uint64_t ntp_timestamp, bool account_for_epoch_diff);
|
||||
uint64_t raop_ntp_timestamp_to_nano_seconds(uint64_t ntp_timestamp, bool account_for_epoch_diff);
|
||||
|
||||
uint64_t raop_ntp_get_local_time(raop_ntp_t *raop_ntp);
|
||||
uint64_t raop_ntp_get_remote_time(raop_ntp_t *raop_ntp);
|
||||
|
||||
182
lib/raop_rtp.c
182
lib/raop_rtp.c
@@ -32,12 +32,12 @@
|
||||
|
||||
#define NO_FLUSH (-42)
|
||||
|
||||
#define RAOP_RTP_SAMPLE_RATE (44100.0 / 1000000.0)
|
||||
#define SECOND_IN_NSECS 1000000000UL
|
||||
#define RAOP_RTP_SYNC_DATA_COUNT 8
|
||||
#define SEC 1000000
|
||||
#define SEC SECOND_IN_NSECS
|
||||
|
||||
#define DELAY_AAC 500000 //empirical, matches audio latency of about -0.5 sec after first clock sync event
|
||||
#define DELAY_ALAC 2000000 //empirical, matches audio latency of about -2.0 sec after first clock sync event
|
||||
#define DELAY_AAC 0.25 //empirical, matches audio latency of about -0.25 sec after first clock sync event
|
||||
#define DELAY_ALAC 2.0 //empirical, matches audio latency of about -2.0 sec after first clock sync event
|
||||
|
||||
/* note: it is unclear what will happen in the unlikely event that this code is running at the time of the unix-time
|
||||
* epoch event on 2038-01-19 at 3:14:08 UTC ! (but Apple will surely have removed AirPlay "legacy pairing" by then!) */
|
||||
@@ -53,7 +53,7 @@ struct raop_rtp_s {
|
||||
|
||||
// Time and sync
|
||||
raop_ntp_t *ntp;
|
||||
double rtp_sync_scale;
|
||||
double rtp_clock_rate;
|
||||
int64_t rtp_sync_offset;
|
||||
raop_rtp_sync_data_t sync_data[RAOP_RTP_SYNC_DATA_COUNT];
|
||||
int sync_data_index;
|
||||
@@ -163,7 +163,6 @@ raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp, co
|
||||
raop_rtp->ntp = ntp;
|
||||
|
||||
raop_rtp->rtp_sync_offset = 0;
|
||||
raop_rtp->rtp_sync_scale = RAOP_RTP_SAMPLE_RATE;
|
||||
raop_rtp->sync_data_index = 0;
|
||||
for (int i = 0; i < RAOP_RTP_SYNC_DATA_COUNT; ++i) {
|
||||
raop_rtp->sync_data[i].ntp_time = 0;
|
||||
@@ -388,34 +387,35 @@ raop_rtp_process_events(raop_rtp_t *raop_rtp, void *cb_data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void raop_rtp_sync_clock(raop_rtp_t *raop_rtp, uint64_t ntp_time, uint64_t rtp_time, int shift) {
|
||||
int latest;
|
||||
uint32_t valid_data_count = 0;
|
||||
valid_data_count = 0;
|
||||
double total_offsets = 0;
|
||||
int64_t rtp_offset, avg_offset, correction;
|
||||
void raop_rtp_sync_clock(raop_rtp_t *raop_rtp, uint64_t *ntp_time, uint64_t *rtp_time) {
|
||||
/* ntp_time = (uint64_t)(((int64_t)(raop_rtp->rtp_clock_rate * rtp_time)) + raop_rtp->rtp_sync_offset) */
|
||||
int latest, valid_data_count = 0;
|
||||
uint64_t ntp_sum = 0, rtp_sum = 0;
|
||||
double offset = ((double) *ntp_time) - raop_rtp->rtp_clock_rate * *rtp_time;
|
||||
int64_t correction = 0;
|
||||
|
||||
raop_rtp->sync_data_index = (raop_rtp->sync_data_index + 1) % RAOP_RTP_SYNC_DATA_COUNT;
|
||||
latest = raop_rtp->sync_data_index;
|
||||
raop_rtp->sync_data[latest].rtp_time = rtp_time;
|
||||
raop_rtp->sync_data[latest].ntp_time = ntp_time;
|
||||
raop_rtp->sync_data[latest].rtp_time = *rtp_time;
|
||||
raop_rtp->sync_data[latest].ntp_time = *ntp_time;
|
||||
|
||||
for (int i = 0; i < RAOP_RTP_SYNC_DATA_COUNT; i++) {
|
||||
if (raop_rtp->sync_data[i].ntp_time == 0) continue;
|
||||
rtp_offset = ((int64_t) raop_rtp->sync_data[i].rtp_time) - ((int64_t) raop_rtp->sync_data[latest].rtp_time);
|
||||
total_offsets += ((double) rtp_offset) / raop_rtp-> rtp_sync_scale;
|
||||
total_offsets -= (double) (((int64_t) raop_rtp->sync_data[i].ntp_time) - ((int64_t) raop_rtp->sync_data[latest].ntp_time));
|
||||
valid_data_count++;
|
||||
if (i == latest) continue;
|
||||
ntp_sum += *ntp_time - raop_rtp->sync_data[i].ntp_time;
|
||||
rtp_sum += *rtp_time - raop_rtp->sync_data[i].rtp_time;
|
||||
}
|
||||
total_offsets = (total_offsets / valid_data_count);
|
||||
rtp_offset = ((int64_t) raop_rtp->sync_data[latest].rtp_time) - ((int64_t) raop_rtp->rtp_start_time) + ((int64_t) shift);
|
||||
total_offsets += ((double) rtp_offset) / raop_rtp->rtp_sync_scale;
|
||||
avg_offset = (int64_t) total_offsets;
|
||||
avg_offset -= ((int64_t) raop_rtp->sync_data[latest].ntp_time) - ((int64_t) raop_rtp->ntp_start_time);
|
||||
correction = avg_offset - raop_rtp->rtp_sync_offset;
|
||||
raop_rtp->rtp_sync_offset = avg_offset;
|
||||
logger_log(raop_rtp->logger, LOGGER_DEBUG, "raop_rtp sync correction=%lld, rtp_sync_offset = %8.6f ",
|
||||
correction, ((double) raop_rtp->rtp_sync_offset) / SEC);
|
||||
|
||||
if (valid_data_count > 1) {
|
||||
correction -= raop_rtp->rtp_sync_offset;
|
||||
offset += (((double) ntp_sum) - raop_rtp->rtp_clock_rate * rtp_sum) / valid_data_count;
|
||||
}
|
||||
raop_rtp->rtp_sync_offset = (int64_t) offset;
|
||||
correction += raop_rtp->rtp_sync_offset;
|
||||
|
||||
logger_log(raop_rtp->logger, LOGGER_DEBUG, "dataset %d raop_rtp sync correction=%lld, rtp_sync_offset = %8.6f ",
|
||||
valid_data_count, correction, offset);
|
||||
}
|
||||
|
||||
uint64_t rtp64_time (raop_rtp_t *raop_rtp, const uint32_t *rtp32) {
|
||||
@@ -451,14 +451,15 @@ raop_rtp_thread_udp(void *arg)
|
||||
struct sockaddr_storage saddr;
|
||||
socklen_t saddrlen;
|
||||
|
||||
double latency = 0;
|
||||
|
||||
/* for initial rtp to ntp conversions */
|
||||
bool have_synced = false;
|
||||
int rtp_count = 0;
|
||||
int64_t initial_offset = 0;
|
||||
double sync_adjustment = 0;
|
||||
int64_t delay = 0;
|
||||
uint64_t delay = 0;
|
||||
unsigned short seqnum1 = 0, seqnum2 = 0;
|
||||
bool offset_estimate_initialized = false;
|
||||
|
||||
|
||||
assert(raop_rtp);
|
||||
raop_rtp->ntp_start_time = raop_ntp_get_local_time(raop_rtp->ntp);
|
||||
@@ -469,7 +470,22 @@ raop_rtp_thread_udp(void *arg)
|
||||
|
||||
logger_log(raop_rtp->logger, LOGGER_DEBUG, "raop_rtp start_time = %8.6f (raop_rtp audio)",
|
||||
((double) raop_rtp->ntp_start_time) / SEC);
|
||||
|
||||
|
||||
switch (raop_rtp->ct) {
|
||||
case 0x02:
|
||||
latency = DELAY_ALAC;
|
||||
delay = (uint64_t)(latency * SECOND_IN_NSECS); /* DELAY = 2.0 sec is empirical choice for ALAC */
|
||||
logger_log(raop_rtp->logger, LOGGER_DEBUG, "Audio is ALAC: using initial latency estimate -%8.6f sec", latency);
|
||||
break;
|
||||
case 0x08:
|
||||
latency = DELAY_AAC;
|
||||
delay = (uint64_t)(latency * SECOND_IN_NSECS); /* DELAY = 0.25 sec is empirical choice for AAC-ELD */
|
||||
logger_log(raop_rtp->logger, LOGGER_DEBUG, "Audio is AAC: using initial latency estimate -%8.6f sec", latency);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
fd_set rfds;
|
||||
struct timeval tv;
|
||||
@@ -520,8 +536,13 @@ raop_rtp_thread_udp(void *arg)
|
||||
if (resent_packetlen >= 12) {
|
||||
uint32_t timestamp = byteutils_get_int_be(resent_packet, 4);
|
||||
uint64_t rtp_time = rtp64_time(raop_rtp, ×tamp);
|
||||
uint64_t ntp_time = 0;
|
||||
if (have_synced) {
|
||||
ntp_time = (uint64_t) (raop_rtp->rtp_sync_offset + (int64_t) (raop_rtp->rtp_clock_rate * rtp_time));
|
||||
ntp_time = raop_ntp_convert_remote_time(raop_rtp->ntp, ntp_time);
|
||||
}
|
||||
logger_log(raop_rtp->logger, LOGGER_DEBUG, "raop_rtp resent audio packet: seqnum=%u", seqnum);
|
||||
int result = raop_buffer_enqueue(raop_rtp->buffer, resent_packet, resent_packetlen, rtp_time, 1);
|
||||
int result = raop_buffer_enqueue(raop_rtp->buffer, resent_packet, resent_packetlen, &ntp_time, &rtp_time, 1);
|
||||
assert(result >= 0);
|
||||
} else {
|
||||
/* type_c = 0x56 packets with length 8 have been reported */
|
||||
@@ -543,32 +564,20 @@ 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);
|
||||
uint64_t sync_rtp64 = rtp64_time(raop_rtp, &sync_rtp);
|
||||
|
||||
|
||||
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_remote = raop_ntp_timestamp_to_nano_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 */
|
||||
shift = -11025; /* 44100/4 */
|
||||
break;
|
||||
case 0x02:
|
||||
default:
|
||||
shift = 0; /* not needed for ALAC (audio only) */
|
||||
break;
|
||||
}
|
||||
char *str = utils_data_to_string(packet, packetlen, 20);
|
||||
logger_log(raop_rtp->logger, LOGGER_DEBUG,
|
||||
"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) raop_rtp->ntp_start_time) / SEC, sync_rtp, str);
|
||||
"raop_rtp sync: client ntp=%8.6f, ntp = %8.6f, ntp_start_time %8.6f\nts_client = %8.6f sync_rtp=%u\n%s",
|
||||
(double) sync_ntp_remote / SEC, (double) sync_ntp_local / SEC,
|
||||
(double) raop_rtp->ntp_start_time / SEC, (double) sync_ntp_remote / SEC, sync_rtp, str);
|
||||
free(str);
|
||||
raop_rtp_sync_clock(raop_rtp, sync_ntp_local, sync_rtp64, shift);
|
||||
raop_rtp_sync_clock(raop_rtp, &sync_ntp_remote, &sync_rtp64);
|
||||
} else {
|
||||
char *str = utils_data_to_string(packet, packetlen, 16);
|
||||
logger_log(raop_rtp->logger, LOGGER_DEBUG, "raop_rtp unknown udp control packet\n%s", str);
|
||||
@@ -621,69 +630,59 @@ raop_rtp_thread_udp(void *arg)
|
||||
int no_resend = (raop_rtp->control_rport == 0); /* true when control_rport is not set */
|
||||
uint32_t rtp_timestamp = byteutils_get_int_be(packet, 4);
|
||||
uint64_t rtp_time = rtp64_time(raop_rtp, &rtp_timestamp);
|
||||
uint64_t ntp_time = 0;
|
||||
if (have_synced == false) {
|
||||
/* until the first rtp sync occurs, we don't know the exact client ntp timestamp that matches the client rtp timestamp */
|
||||
int64_t sync_ntp = ((int64_t) raop_ntp_get_local_time(raop_rtp->ntp)) - ((int64_t) raop_rtp->ntp_start_time) ;
|
||||
int64_t sync_rtp = ((int64_t) rtp_time) - ((int64_t) raop_rtp->rtp_start_time);
|
||||
int64_t offset;
|
||||
unsigned short seqnum = byteutils_get_short_be(packet,2);
|
||||
if (!offset_estimate_initialized) {
|
||||
offset_estimate_initialized = true;
|
||||
switch (raop_rtp->ct) {
|
||||
case 0x02:
|
||||
delay = DELAY_ALAC; /* DELAY = 2000000 (2.0 sec) is empirical choice for ALAC */
|
||||
logger_log(raop_rtp->logger, LOGGER_DEBUG, "Audio is ALAC: using initial latency estimate -%8.6f sec",
|
||||
((double) delay) / SEC);
|
||||
break;
|
||||
case 0x08:
|
||||
delay = DELAY_AAC; /* DELAY = 500000 (0.5 sec) is empirical choice for AAC-ELD */
|
||||
logger_log(raop_rtp->logger, LOGGER_DEBUG, "Audio is AAC: using initial latency estimate -%8.6f sec",
|
||||
((double) delay ) / SEC);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
initial_offset = -(sync_ntp + delay);
|
||||
raop_rtp->rtp_sync_offset = initial_offset;
|
||||
sync_adjustment = 0;
|
||||
if (rtp_count == 0) {
|
||||
sync_adjustment = ((double) sync_ntp);
|
||||
rtp_count = 1;
|
||||
seqnum1 = seqnum;
|
||||
seqnum2 = seqnum;
|
||||
}
|
||||
sync_ntp += delay;
|
||||
offset = -sync_ntp;
|
||||
if (seqnum2 != seqnum) { /* for AAC-ELD only use copy 3 of the 3 copies of each frame */
|
||||
if (seqnum2 != seqnum) { /* for AAC-ELD only use copy 1 of the 3 copies of each frame */
|
||||
rtp_count++;
|
||||
offset -= initial_offset;
|
||||
sync_adjustment += ((double) offset) + (((double) sync_rtp) / raop_rtp->rtp_sync_scale);
|
||||
raop_rtp->rtp_sync_offset = initial_offset + (int64_t) (sync_adjustment / rtp_count);
|
||||
//logger_log(raop_rtp->logger, LOGGER_DEBUG, "initial estimate of rtp_sync_offset %d secnum = %u: %8.6f",
|
||||
// rtp_count, seqnum, ((double) raop_rtp->rtp_sync_offset) / SEC);
|
||||
sync_adjustment += (((double) sync_ntp) - raop_rtp->rtp_clock_rate * sync_rtp - sync_adjustment) / rtp_count;
|
||||
}
|
||||
seqnum2 = seqnum1;
|
||||
seqnum1 = seqnum;
|
||||
} else {
|
||||
ntp_time = (uint64_t) (raop_rtp->rtp_sync_offset + (int64_t) (raop_rtp->rtp_clock_rate * rtp_time));
|
||||
ntp_time = raop_ntp_convert_remote_time(raop_rtp->ntp, ntp_time);
|
||||
}
|
||||
int result = raop_buffer_enqueue(raop_rtp->buffer, packet, packetlen, rtp_time, 1);
|
||||
int result = raop_buffer_enqueue(raop_rtp->buffer, packet, packetlen, &ntp_time, &rtp_time, 1);
|
||||
assert(result >= 0);
|
||||
// Render continuous buffer entries
|
||||
void *payload = NULL;
|
||||
unsigned int payload_size;
|
||||
unsigned short seqnum;
|
||||
uint64_t rtp64_timestamp;
|
||||
while ((payload = raop_buffer_dequeue(raop_rtp->buffer, &payload_size, &rtp64_timestamp, &seqnum, no_resend))) {
|
||||
double elapsed_time = (((double) (rtp64_timestamp - (uint64_t) raop_rtp->rtp_start_time)) / raop_rtp->rtp_sync_scale);
|
||||
uint64_t ntp_timestamp;
|
||||
while ((payload = raop_buffer_dequeue(raop_rtp->buffer, &payload_size, &ntp_timestamp, &rtp64_timestamp, &seqnum, no_resend))) {
|
||||
audio_decode_struct audio_data;
|
||||
audio_data.data_len = payload_size;
|
||||
audio_data.data = payload;
|
||||
audio_data.ntp_time = raop_rtp->ntp_start_time + (uint64_t) elapsed_time;
|
||||
audio_data.ntp_time -= raop_rtp->rtp_sync_offset;
|
||||
audio_data.rtp_time = rtp64_timestamp;
|
||||
audio_data.seqnum = seqnum;
|
||||
audio_data.data_len = payload_size;
|
||||
audio_data.data = payload;
|
||||
if (ntp_timestamp) {
|
||||
audio_data.ntp_time = ntp_timestamp;
|
||||
} else {
|
||||
if (have_synced) {
|
||||
uint64_t ntp_remote = (uint64_t) (raop_rtp->rtp_sync_offset + (int64_t) (raop_rtp->rtp_clock_rate * rtp64_timestamp));
|
||||
audio_data.ntp_time = raop_ntp_convert_remote_time(raop_rtp->ntp, ntp_remote);
|
||||
} else {
|
||||
audio_data.ntp_time = raop_rtp->ntp_start_time + delay +
|
||||
(uint64_t) (sync_adjustment + raop_rtp->rtp_clock_rate * (rtp64_timestamp - raop_rtp->rtp_start_time));
|
||||
}
|
||||
}
|
||||
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);
|
||||
int64_t latency = ((int64_t) ntp_now) - ((int64_t) audio_data.ntp_time);
|
||||
logger_log(raop_rtp->logger, LOGGER_DEBUG, "raop_rtp audio: now = %8.6f, npt = %8.6f, latency = %8.6f, rtp_time=%u seqnum = %u",
|
||||
((double) ntp_now ) / SEC, ((double) audio_data.ntp_time) / SEC, ((double) latency) / SEC, (uint32_t) rtp64_timestamp,
|
||||
int64_t latency = ((int64_t) ntp_now) - ((int64_t) audio_data.ntp_time);
|
||||
logger_log(raop_rtp->logger, LOGGER_DEBUG, "raop_rtp audio: now = %8.6f, ntp = %8.6f, latency = %8.6f, rtp_time=%u seqnum = %u",
|
||||
((double) ntp_now ) / SEC, ((double) audio_data.ntp_time) / SEC, ((double) latency) / SEC, (uint32_t) rtp64_timestamp,
|
||||
seqnum);
|
||||
}
|
||||
|
||||
@@ -711,15 +710,13 @@ raop_rtp_thread_udp(void *arg)
|
||||
|
||||
// Start rtp service, three udp ports
|
||||
void
|
||||
raop_rtp_start_audio(raop_rtp_t *raop_rtp, int use_udp, unsigned short control_rport,
|
||||
unsigned short *control_lport, unsigned short *data_lport, unsigned char ct)
|
||||
raop_rtp_start_audio(raop_rtp_t *raop_rtp, int use_udp, unsigned short *control_rport, unsigned short *control_lport,
|
||||
unsigned short *data_lport, unsigned char *ct, unsigned int *sr)
|
||||
{
|
||||
logger_log(raop_rtp->logger, LOGGER_INFO, "raop_rtp starting audio");
|
||||
int use_ipv6 = 0;
|
||||
|
||||
assert(raop_rtp);
|
||||
assert(control_lport);
|
||||
assert(data_lport);
|
||||
|
||||
MUTEX_LOCK(raop_rtp->run_mutex);
|
||||
if (raop_rtp->running || !raop_rtp->joined) {
|
||||
@@ -727,12 +724,13 @@ raop_rtp_start_audio(raop_rtp_t *raop_rtp, int use_udp, unsigned short control_r
|
||||
return;
|
||||
}
|
||||
|
||||
raop_rtp->ct = ct;
|
||||
raop_rtp->ct = *ct;
|
||||
raop_rtp->rtp_clock_rate = SECOND_IN_NSECS / *sr;
|
||||
|
||||
/* Initialize ports and sockets */
|
||||
raop_rtp->control_lport = *control_lport;
|
||||
raop_rtp->data_lport = *data_lport;
|
||||
raop_rtp->control_rport = control_rport;
|
||||
raop_rtp->control_rport = *control_rport;
|
||||
if (raop_rtp->remote_saddr.ss_family == AF_INET6) {
|
||||
use_ipv6 = 1;
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ typedef struct raop_rtp_s raop_rtp_t;
|
||||
raop_rtp_t *raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp, const unsigned char *remote,
|
||||
int remotelen, const unsigned char *aeskey, const unsigned char *aesiv);
|
||||
|
||||
void raop_rtp_start_audio(raop_rtp_t *raop_rtp, int use_udp, unsigned short control_rport,
|
||||
unsigned short *control_lport, unsigned short *data_lport, unsigned char ct);
|
||||
void raop_rtp_start_audio(raop_rtp_t *raop_rtp, int use_udp, unsigned short *control_rport, unsigned short *control_lport,
|
||||
unsigned short *data_lport, unsigned char *ct, unsigned int *sr);
|
||||
|
||||
void raop_rtp_set_volume(raop_rtp_t *raop_rtp, float volume);
|
||||
void raop_rtp_set_metadata(raop_rtp_t *raop_rtp, const char *data, int datalen);
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
#define CAST
|
||||
#endif
|
||||
|
||||
#define SEC 1000000
|
||||
#define SECOND_IN_NSECS 1000000000UL
|
||||
#define SEC SECOND_IN_NSECS
|
||||
|
||||
/* for MacOS, where SOL_TCP and TCP_KEEPIDLE are not defined */
|
||||
#if !defined(SOL_TCP) && defined(IPPROTO_TCP)
|
||||
@@ -198,6 +199,8 @@ raop_rtp_mirror_thread(void *arg)
|
||||
bool conn_reset = false;
|
||||
uint64_t ntp_timestamp_nal = 0;
|
||||
uint64_t ntp_timestamp_raw = 0;
|
||||
uint64_t ntp_timestamp_remote = 0;
|
||||
uint64_t ntp_timestamp;
|
||||
unsigned char nal_start_code[4] = { 0x00, 0x00, 0x00, 0x01 };
|
||||
|
||||
#ifdef DUMP_H264
|
||||
@@ -308,7 +311,15 @@ raop_rtp_mirror_thread(void *arg)
|
||||
|
||||
/*packet[0:3] contains the payload size */
|
||||
int payload_size = byteutils_get_int(packet, 0);
|
||||
|
||||
char packet_description[13] = {0};
|
||||
char *p = packet_description;
|
||||
for (int i = 4; i < 8; i++) {
|
||||
sprintf(p, "%2.2x ", (unsigned int) packet[i]);
|
||||
p += 3;
|
||||
}
|
||||
ntp_timestamp_raw = byteutils_get_long(packet, 8);
|
||||
ntp_timestamp_remote = raop_ntp_timestamp_to_nano_seconds(ntp_timestamp_raw, false);
|
||||
|
||||
/* packet[4] appears to have one of three possible values: *
|
||||
* 0x00 : encrypted packet *
|
||||
* 0x01 : unencrypted packet with a SPS and a PPS NAL, sent initially, and also when *
|
||||
@@ -359,15 +370,13 @@ raop_rtp_mirror_thread(void *arg)
|
||||
// Conveniently, the video data is already stamped with the remote wall clock time,
|
||||
// so no additional clock syncing needed. The only thing odd here is that the video
|
||||
// ntp time stamps don't include the SECONDS_FROM_1900_TO_1970, so it's really just
|
||||
// counting micro seconds since last boot.
|
||||
ntp_timestamp_raw = byteutils_get_long(packet, 8);
|
||||
uint64_t ntp_timestamp_remote = raop_ntp_timestamp_to_micro_seconds(ntp_timestamp_raw, false);
|
||||
uint64_t ntp_timestamp = raop_ntp_convert_remote_time(raop_rtp_mirror->ntp, ntp_timestamp_remote);
|
||||
// counting nano seconds since last boot.
|
||||
|
||||
ntp_timestamp = raop_ntp_convert_remote_time(raop_rtp_mirror->ntp, ntp_timestamp_remote);
|
||||
uint64_t ntp_now = raop_ntp_get_local_time(raop_rtp_mirror->ntp);
|
||||
int64_t latency = ((int64_t) ntp_now) - ((int64_t) ntp_timestamp);
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp video: now = %8.6f, ntp = %8.6f, latency = %8.6f",
|
||||
((double) ntp_now) / SEC, ((double) ntp_timestamp) / SEC, ((double) latency) / SEC);
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp video: now = %8.6f, ntp = %8.6f, latency = %8.6f, ts = %8.6f, %s",
|
||||
(double) ntp_now / SEC, (double) ntp_timestamp / SEC, (double) latency / SEC, (double) ntp_timestamp_remote / SEC, packet_description);
|
||||
|
||||
#ifdef DUMP_H264
|
||||
fwrite(payload, payload_size, 1, file_source);
|
||||
@@ -443,7 +452,7 @@ raop_rtp_mirror_thread(void *arg)
|
||||
if (prepend_sps_pps) {
|
||||
h264_data.data_len += raop_rtp_mirror->sps_pps_len;
|
||||
h264_data.nal_count += 2;
|
||||
if (ntp_timestamp_raw != ntp_timestamp_nal) {
|
||||
if (ntp_timestamp_remote != ntp_timestamp_nal) {
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "raop_rtp_mirror: prepended sps_pps timestamp does not match that of video payload");
|
||||
}
|
||||
}
|
||||
@@ -453,11 +462,13 @@ raop_rtp_mirror_thread(void *arg)
|
||||
case 0x01:
|
||||
// The information in the payload contains an SPS and a PPS NAL
|
||||
// The sps_pps is not encrypted
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "\nReceived unencryted codec packet from client: payload_size %d header %s ts_client = %8.6f",
|
||||
payload_size, packet_description, (double) ntp_timestamp_remote / SEC);
|
||||
if (payload_size == 0) {
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "raop_rtp_mirror, discard type 0x01 packet with no payload");
|
||||
break;
|
||||
}
|
||||
ntp_timestamp_nal = byteutils_get_long(packet, 8);
|
||||
ntp_timestamp_nal = ntp_timestamp_remote;
|
||||
float width = byteutils_get_float(packet, 16);
|
||||
float height = byteutils_get_float(packet, 20);
|
||||
float width_source = byteutils_get_float(packet, 40);
|
||||
@@ -537,7 +548,8 @@ raop_rtp_mirror_thread(void *arg)
|
||||
|
||||
break;
|
||||
case 0x05:
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "\nReceived video streaming performance info packet from client");
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_DEBUG, "\nReceived video streaming performance info packet from client: payload_size %d header %s ts_raw = %llu",
|
||||
payload_size, packet_description, ntp_timestamp_raw);
|
||||
/* payloads with packet[4] = 0x05 have no timestamp, and carry video info from the client as a binary plist *
|
||||
* Sometimes (e.g, when the client has a locked screen), there is a 25kB trailer attached to the packet. *
|
||||
* This 25000 Byte trailer with unidentified content seems to be the same data each time it is sent. */
|
||||
@@ -566,7 +578,8 @@ raop_rtp_mirror_thread(void *arg)
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "\nReceived unexpected TCP packet from client, packet[4] = 0x%2.2x", packet[4]);
|
||||
logger_log(raop_rtp_mirror->logger, LOGGER_WARNING, "\nReceived unexpected TCP packet from client, size %d, %s ts_raw = raw%llu",
|
||||
payload_size, packet_description, ntp_timestamp_raw);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ extern "C" {
|
||||
#include "../lib/logger.h"
|
||||
|
||||
bool gstreamer_init();
|
||||
void audio_renderer_init(logger_t *logger, const char* audiosink, const char* audiodelay);
|
||||
void audio_renderer_init(logger_t *logger, const char* audiosink, const bool *audio_sync);
|
||||
void audio_renderer_start(unsigned char* compression_type);
|
||||
void audio_renderer_stop();
|
||||
void audio_renderer_render_buffer(unsigned char* data, int *data_len, unsigned short *seqnum, uint64_t *ntp_time);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <gst/gst.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
#include "audio_renderer.h"
|
||||
#define SECOND_IN_NSECS 1000000000UL
|
||||
|
||||
/* GStreamer Caps strings for Airplay-defined audio compression types (ct) */
|
||||
|
||||
@@ -81,7 +82,7 @@ static GstClockTime gst_audio_pipeline_base_time = GST_CLOCK_TIME_NONE;
|
||||
static logger_t *logger = NULL;
|
||||
const char * format[NFORMATS];
|
||||
|
||||
void audio_renderer_init(logger_t *render_logger, const char* audiosink, const char* audio_delay) {
|
||||
void audio_renderer_init(logger_t *render_logger, const char* audiosink, const bool* audio_sync) {
|
||||
GError *error = NULL;
|
||||
GstCaps *caps = NULL;
|
||||
GstClock *clock = gst_system_clock_obtain();
|
||||
@@ -100,11 +101,6 @@ void audio_renderer_init(logger_t *render_logger, const char* audiosink, const c
|
||||
g_string_append(launch, "! avdec_aac ! ");
|
||||
break;
|
||||
case 1: /* ALAC */
|
||||
if (audio_delay[0]) {
|
||||
g_string_append(launch, "min-threshold-time=");
|
||||
g_string_append(launch, audio_delay);
|
||||
g_string_append(launch, "000000 ");
|
||||
}
|
||||
g_string_append(launch, "! avdec_alac ! ");
|
||||
break;
|
||||
case 3: /*PCM*/
|
||||
@@ -116,7 +112,18 @@ void audio_renderer_init(logger_t *render_logger, const char* audiosink, const c
|
||||
g_string_append (launch, "audioresample ! "); /* wasapisink must resample from 44.1 kHz to 48 kHz */
|
||||
g_string_append (launch, "volume name=volume ! level ! ");
|
||||
g_string_append (launch, audiosink);
|
||||
g_string_append (launch, " sync=false");
|
||||
switch(i) {
|
||||
case 1: /*ALAC*/
|
||||
if (*audio_sync) {
|
||||
g_string_append (launch, " sync=true");
|
||||
} else {
|
||||
g_string_append (launch, " sync=false");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
g_string_append (launch, " sync=false");
|
||||
break;
|
||||
}
|
||||
renderer_type[i]->pipeline = gst_parse_launch(launch->str, &error);
|
||||
if (error) {
|
||||
g_error ("gst_parse_launch error (audio %d):\n %s\n", i+1, error->message);
|
||||
@@ -201,11 +208,12 @@ void audio_renderer_start(unsigned char *ct) {
|
||||
void audio_renderer_render_buffer(unsigned char* data, int *data_len, unsigned short *seqnum, uint64_t *ntp_time) {
|
||||
GstBuffer *buffer;
|
||||
bool valid;
|
||||
GstClockTime pts = (GstClockTime) (*ntp_time * 1000); /* convert from usec to nsec */
|
||||
GstClockTime pts = (GstClockTime) *ntp_time ; /* now in nsecs */
|
||||
if (pts >= gst_audio_pipeline_base_time) {
|
||||
pts -= gst_audio_pipeline_base_time;
|
||||
} else {
|
||||
logger_log(logger, LOGGER_ERR, "*** invalid *pts_raw < gst_audio_pipeline_base_time");
|
||||
logger_log(logger, LOGGER_ERR, "*** invalid ntp_time < gst_audio_pipeline_base_time\n%8.6f ntp_time\n%8.6f base_time",
|
||||
((double) *ntp_time) / SECOND_IN_NSECS, ((double) gst_audio_pipeline_base_time) / SECOND_IN_NSECS);
|
||||
return;
|
||||
}
|
||||
if (data_len == 0 || renderer == NULL) return;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <gst/gst.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
|
||||
#define SECOND_IN_NSECS 1000000000UL
|
||||
#ifdef X_DISPLAY_FIX
|
||||
#include <gst/video/navigation.h>
|
||||
#include "x_display_fix.h"
|
||||
@@ -220,11 +221,12 @@ void video_renderer_start() {
|
||||
|
||||
void video_renderer_render_buffer(unsigned char* data, int *data_len, int *nal_count, uint64_t *ntp_time) {
|
||||
GstBuffer *buffer;
|
||||
GstClockTime pts = (GstClockTime) (*ntp_time * 1000); /*convert from usec to nsec */
|
||||
GstClockTime pts = (GstClockTime) *ntp_time; /*now in nsecs */
|
||||
if (pts >= gst_video_pipeline_base_time) {
|
||||
pts -= gst_video_pipeline_base_time;
|
||||
} else {
|
||||
logger_log(logger, LOGGER_ERR, "*** invalid *pts_raw < gst_video_pipeline_base_time") ;
|
||||
logger_log(logger, LOGGER_ERR, "*** invalid ntp_time < gst_video_pipeline_base_time\n%8.6f ntp_time\n%8.6f base_time",
|
||||
((double) *ntp_time) / SECOND_IN_NSECS, ((double) gst_video_pipeline_base_time) / SECOND_IN_NSECS);
|
||||
return;
|
||||
}
|
||||
g_assert(data_len != 0);
|
||||
|
||||
9
uxplay.1
9
uxplay.1
@@ -1,11 +1,11 @@
|
||||
.TH UXPLAY "1" "January 2023" "1.62" "User Commands"
|
||||
.TH UXPLAY "1" "February 2023" "1.63" "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.62: An open\-source AirPlay mirroring (+ audio streaming) server.
|
||||
UxPlay 1.63: An open\-source AirPlay mirroring (+ audio streaming) server.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B
|
||||
@@ -13,7 +13,8 @@ UxPlay 1.62: An open\-source AirPlay mirroring (+ audio streaming) server.
|
||||
.TP
|
||||
\fB\-nh\fR Do \fBNOT\fR append "@\fIhostname\fR" at end of the AirPlay server name
|
||||
.TP
|
||||
.B
|
||||
\fB\-sync\fR (In Audio-Only mode) sync audio on server with video on client.
|
||||
.TP
|
||||
\fB\-s\fR wxh[@r]Set display resolution [refresh_rate] default 1920x1080[@60]
|
||||
.TP
|
||||
\fB\-o\fR Set display "overscanned" mode on (not usually needed)
|
||||
@@ -73,7 +74,7 @@ UxPlay 1.62: An open\-source AirPlay mirroring (+ audio streaming) server.
|
||||
.TP
|
||||
\fB\-as\fR 0 (or \fB\-a\fR) Turn audio off, streamed video only.
|
||||
.TP
|
||||
\fB\-ao\fR x.y Audio offset time in seconds (default 0.0) in Audio-only mode.
|
||||
\fB\-al\fR x Audio latency in seconds (default 0.25) reported to client.
|
||||
.TP
|
||||
\fB\-ca\fI fn \fR In Airplay Audio (ALAC) mode, write cover-art to file fn.
|
||||
.TP
|
||||
|
||||
36
uxplay.cpp
36
uxplay.cpp
@@ -51,8 +51,9 @@
|
||||
#include "renderers/video_renderer.h"
|
||||
#include "renderers/audio_renderer.h"
|
||||
|
||||
#define VERSION "1.62"
|
||||
#define VERSION "1.63"
|
||||
|
||||
#define SECOND_IN_USECS 1000000
|
||||
#define DEFAULT_NAME "UxPlay"
|
||||
#define DEFAULT_DEBUG_LOG false
|
||||
#define LOWEST_ALLOWED_PORT 1024
|
||||
@@ -64,7 +65,7 @@ static std::string server_name = DEFAULT_NAME;
|
||||
static dnssd_t *dnssd = NULL;
|
||||
static raop_t *raop = NULL;
|
||||
static logger_t *render_logger = NULL;
|
||||
|
||||
static bool audio_sync = false;
|
||||
static bool relaunch_video = false;
|
||||
static bool reset_loop = false;
|
||||
static unsigned int open_connections= 0;
|
||||
@@ -73,7 +74,7 @@ static videoflip_t videoflip[2] = { NONE , NONE };
|
||||
static bool use_video = true;
|
||||
static unsigned char compression_type = 0;
|
||||
static std::string audiosink = "autoaudiosink";
|
||||
static std::string audiodelay = "";
|
||||
static int audiodelay = -1;
|
||||
static bool use_audio = true;
|
||||
static bool new_window_closing_behavior = true;
|
||||
static bool close_window;
|
||||
@@ -358,6 +359,7 @@ static void print_info (char *name) {
|
||||
printf("Options:\n");
|
||||
printf("-n name Specify the network name of the AirPlay server\n");
|
||||
printf("-nh Do not add \"@hostname\" at the end of the AirPlay server name\n");
|
||||
printf("-sync (In Audio-Only mode) sync audio on server with video on client\n");
|
||||
printf("-s wxh[@r]Set display resolution [refresh_rate] default 1920x1080[@60]\n");
|
||||
printf("-o Set display \"overscanned\" mode on (not usually needed)\n");
|
||||
printf("-fs Full-screen (only works with X11, Wayland and VAAPI)\n");
|
||||
@@ -387,7 +389,7 @@ static void print_info (char *name) {
|
||||
printf(" some choices:pulsesink,alsasink,pipewiresink,jackaudiosink,\n");
|
||||
printf(" osssink,oss4sink,osxaudiosink,wasapisink,directsoundsink.\n");
|
||||
printf("-as 0 (or -a) Turn audio off, streamed video only\n");
|
||||
printf("-ao x.y Audio offset time in seconds (default 0.0) in Audio-only mode.\n");
|
||||
printf("-al x Audio latency in seconds (default 0.25) reported to client.\n");
|
||||
printf("-ca <fn> In Airplay Audio (ALAC) mode, write cover-art to file <fn>\n");
|
||||
printf("-reset n Reset after 3n seconds client silence (default %d, 0=never)\n", NTP_TIMEOUT_LIMIT);
|
||||
printf("-nc do Not Close video window when client stops mirroring\n");
|
||||
@@ -551,6 +553,8 @@ static void parse_arguments (int argc, char *argv[]) {
|
||||
server_name = std::string(argv[++i]);
|
||||
} else if (arg == "-nh") {
|
||||
do_append_hostname = false;
|
||||
} else if (arg == "-sync") {
|
||||
audio_sync = true;
|
||||
} else if (arg == "-s") {
|
||||
if (!option_has_value(i, argc, argv[i], argv[i+1])) exit(1);
|
||||
std::string value(argv[++i]);
|
||||
@@ -737,24 +741,18 @@ static void parse_arguments (int argc, char *argv[]) {
|
||||
bt709_fix = true;
|
||||
} else if (arg == "-nohold") {
|
||||
max_connections = 3;
|
||||
} else if (arg == "-ao") {
|
||||
} else if (arg == "-al") {
|
||||
int n;
|
||||
char *end;
|
||||
if (i < argc - 1 && *argv[i+1] != '-') {
|
||||
n = (int) (1000 * strtof(argv[++i], &end));
|
||||
if (*end == '\0' && n >=0 && n <= 10000) {
|
||||
audiodelay.erase();
|
||||
if (n > 0) {
|
||||
char* delay = new char[6];
|
||||
snprintf(delay, 6, "%d", n);
|
||||
audiodelay = delay;
|
||||
delete[] delay;
|
||||
}
|
||||
n = (int) (strtof(argv[++i], &end) * SECOND_IN_USECS);
|
||||
if (*end == '\0' && n >=0 && n <= 10 * SECOND_IN_USECS) {
|
||||
audiodelay = n;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "invalid argument -ao %s: must be a decimal time offset in seconds, range [0,10]\n"
|
||||
"(like 5 or 4.8, which will be converted to a whole number of milliseconds)\n", argv[i]);
|
||||
fprintf(stderr, "invalid argument -al %s: must be a decimal time offset in seconds, range [0,10]\n"
|
||||
"(like 5 or 4.8, which will be converted to a whole number of microseconds)\n", argv[i]);
|
||||
exit(1);
|
||||
} else {
|
||||
fprintf(stderr, "unknown option %s, stopping\n",argv[i]);
|
||||
@@ -1162,6 +1160,7 @@ int start_raop_server (unsigned short display[5], unsigned short tcp[3], unsigne
|
||||
|
||||
if (show_client_FPS_data) raop_set_plist(raop, "clientFPSdata", 1);
|
||||
raop_set_plist(raop, "max_ntp_timeouts", max_ntp_timeouts);
|
||||
if (audiodelay >= 0) raop_set_plist(raop, "audio_delay_micros", audiodelay);
|
||||
|
||||
/* network port selection (ports listed as "0" will be dynamically assigned) */
|
||||
raop_set_tcp_ports(raop, tcp);
|
||||
@@ -1284,10 +1283,7 @@ int main (int argc, char *argv[]) {
|
||||
logger_set_level(render_logger, debug_log ? LOGGER_DEBUG : LOGGER_INFO);
|
||||
|
||||
if (use_audio) {
|
||||
if (audiodelay.c_str()[0]) {
|
||||
LOGI("Audio-only ALAC streams will be delayed by %s milliseconds", audiodelay.c_str());
|
||||
}
|
||||
audio_renderer_init(render_logger, audiosink.c_str(), audiodelay.c_str());
|
||||
audio_renderer_init(render_logger, audiosink.c_str(), &audio_sync);
|
||||
} else {
|
||||
LOGI("audio_disabled");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user