Use CLOCK_MONOLITHIC_RAW to avoid NTP effects, report raw and corrected timing from audio_alsa. Clean up statistics. Stop watchdog timing out AP2 sessions. CLean up some debug messages.

This commit is contained in:
Mike Brady
2021-11-28 16:08:48 +00:00
parent dc5b8f1a11
commit 56bef8e7dd
11 changed files with 137 additions and 99 deletions
+1 -1
View File
@@ -187,7 +187,7 @@ void *activity_monitor_thread_code(void *arg) {
uint64_t time_to_wait_for_wakeup_ns = (uint64_t)(config.active_state_timeout * 1000000000);
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
uint64_t time_of_wakeup_ns = get_realtime_in_ns() + time_to_wait_for_wakeup_ns;
sec = time_of_wakeup_ns / 1000000000;
nsec = time_of_wakeup_ns % 1000000000;
time_for_wait.tv_sec = sec;
+2 -1
View File
@@ -39,7 +39,8 @@ typedef struct {
// will change dynamically, so keep watching it. Implemented in ALSA only.
// returns a negative error code if there's a problem
int (*delay)(long *the_delay); // snd_pcm_sframes_t is a signed long
int (*stats)(uint64_t *measurement_time, uint64_t *delay,
int (*stats)(uint64_t *raw_measurement_time, uint64_t *corrected_measurement_time,
uint64_t *delay,
uint64_t *frames_sent_to_dac); // use this to get the true rate of the DAC
// may be NULL, in which case soft volume is applied
+8 -4
View File
@@ -60,8 +60,9 @@ static int play(void *buf, int samples);
static void stop(void);
static void flush(void);
int delay(long *the_delay);
int stats(uint64_t *the_time, uint64_t *the_delay, uint64_t *frames_sent_to_dac);
// int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played);
int stats(uint64_t *raw_measurement_time, uint64_t *corrected_measurement_time, uint64_t *the_delay,
uint64_t *frames_sent_to_dac);
void *alsa_buffer_monitor_thread_code(void *arg);
static void volume(double vol);
@@ -1633,7 +1634,8 @@ int delay(long *the_delay) {
return ret;
}
int stats(uint64_t *the_time, uint64_t *the_delay, uint64_t *frames_sent_to_dac) {
int stats(uint64_t *raw_measurement_time, uint64_t *corrected_measurement_time, uint64_t *the_delay,
uint64_t *frames_sent_to_dac) {
// returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or
// SND_PCM_STATE_PREPARED
// or SND_PCM_STATE_DRAINING.
@@ -1654,7 +1656,9 @@ int stats(uint64_t *the_time, uint64_t *the_delay, uint64_t *frames_sent_to_dac)
if (alsa_handle == NULL) {
ret = ENODEV;
} else {
*the_time = get_absolute_time_in_ns();
*raw_measurement_time =
get_absolute_time_in_ns(); // this is not conditioned ("disciplined") by NTP
*corrected_measurement_time = get_monotonic_time_in_ns(); // this is ("disciplined") by NTP
ret = delay_and_status(&state, &my_delay, NULL);
}
if (ret == 0)
+66 -56
View File
@@ -1210,58 +1210,6 @@ double vol2attn(double vol, long max_db, long min_db) {
return vol_setting;
}
uint64_t get_absolute_time_in_fp() {
uint64_t time_now_fp;
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
struct timespec tn;
// can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt
clock_gettime(CLOCK_MONOTONIC, &tn);
uint64_t tnfpsec = tn.tv_sec;
if (tnfpsec > 0x100000000)
warn("clock_gettime seconds overflow!");
uint64_t tnfpnsec = tn.tv_nsec;
if (tnfpnsec > 0x100000000)
warn("clock_gettime nanoseconds seconds overflow!");
tnfpsec = tnfpsec << 32;
tnfpnsec = tnfpnsec << 32;
tnfpnsec = tnfpnsec / 1000000000;
time_now_fp = tnfpsec + tnfpnsec; // types okay
#endif
#ifdef COMPILE_FOR_OSX
uint64_t time_now_mach;
uint64_t elapsedNano;
static mach_timebase_info_data_t sTimebaseInfo = {0, 0};
time_now_mach = mach_absolute_time();
// If this is the first time we've run, get the timebase.
// We can use denom == 0 to indicate that sTimebaseInfo is
// uninitialised because it makes no sense to have a zero
// denominator in a fraction.
if (sTimebaseInfo.denom == 0) {
debug(1, "Mac initialise timebase info.");
(void)mach_timebase_info(&sTimebaseInfo);
}
// Do the maths. We hope that the multiplication doesn't
// overflow; the price you pay for working in fixed point.
if (sTimebaseInfo.denom == 0)
die("could not initialise Mac timebase info in get_absolute_time_in_fp().")
// this gives us nanoseconds
uint64_t time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
// take the units and shift them to the upper half of the fp, and take the nanoseconds, shift them
// to the upper half and then divide the result to 1000000000
time_now_fp =
((time_now_ns / 1000000000) << 32) + (((time_now_ns % 1000000000) << 32) / 1000000000);
#endif
return time_now_fp;
}
uint64_t get_monotonic_time_in_ns() {
uint64_t time_now_ns;
@@ -1308,10 +1256,57 @@ uint64_t get_monotonic_time_in_ns() {
return time_now_ns;
}
uint64_t get_absolute_time_in_ns() {
// all these clock things are now in macOS now since 10.13 (September 2017). Must update...
uint64_t get_monotonic_raw_time_in_ns() {
// CLOCK_MONOTONIC_RAW in FreeBSD etc, monotonic in MacOSX
uint64_t time_now_ns;
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
struct timespec tn;
clock_gettime(CLOCK_MONOTONIC_RAW, &tn);
uint64_t tnnsec = tn.tv_sec;
tnnsec = tnnsec * 1000000000;
uint64_t tnjnsec = tn.tv_nsec;
time_now_ns = tnnsec + tnjnsec;
#endif
#ifdef COMPILE_FOR_OSX
uint64_t time_now_mach;
uint64_t elapsedNano;
static mach_timebase_info_data_t sTimebaseInfo = {0, 0};
// this actually give you a monotonic clock
// see https://news.ycombinator.com/item?id=6303755
time_now_mach = mach_absolute_time();
// If this is the first time we've run, get the timebase.
// We can use denom == 0 to indicate that sTimebaseInfo is
// uninitialised because it makes no sense to have a zero
// denominator in a fraction.
if (sTimebaseInfo.denom == 0) {
debug(1, "Mac initialise timebase info.");
(void)mach_timebase_info(&sTimebaseInfo);
}
if (sTimebaseInfo.denom == 0)
die("could not initialise Mac timebase info in get_monotonic_time_in_ns().")
// Do the maths. We hope that the multiplication doesn't
// overflow; the price you pay for working in fixed point.
// this gives us nanoseconds
time_now_ns = time_now_mach * sTimebaseInfo.numer / sTimebaseInfo.denom;
#endif
return time_now_ns;
}
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
// Not defined for macOS
uint64_t get_realtime_in_ns() {
uint64_t time_now_ns;
struct timespec tn;
// can't use CLOCK_MONOTONIC_RAW as it's not implemented in OpenWrt
// CLOCK_REALTIME because PTP uses it.
@@ -1320,6 +1315,21 @@ uint64_t get_absolute_time_in_ns() {
tnnsec = tnnsec * 1000000000;
uint64_t tnjnsec = tn.tv_nsec;
time_now_ns = tnnsec + tnjnsec;
return time_now_ns;
}
#endif
uint64_t get_absolute_time_in_ns() {
// CLOCK_MONOTONIC_RAW in FreeBSD etc, monotonic in MacOSX
uint64_t time_now_ns;
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
struct timespec tn;
clock_gettime(CLOCK_MONOTONIC_RAW, &tn);
uint64_t tnnsec = tn.tv_sec;
tnnsec = tnnsec * 1000000000;
uint64_t tnjnsec = tn.tv_nsec;
time_now_ns = tnnsec + tnjnsec;
#endif
#ifdef COMPILE_FOR_OSX
@@ -1521,8 +1531,8 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);
struct timespec timeoutTime;
uint64_t wait_until_time = dally_time * 1000; // to nanoseconds
uint64_t start_time = get_absolute_time_in_ns(); // this is from CLOCK_REALTIME
uint64_t wait_until_time = dally_time * 1000; // to nanoseconds
uint64_t start_time = get_realtime_in_ns(); // this is from CLOCK_REALTIME
wait_until_time = wait_until_time + start_time;
uint64_t wait_until_sec = wait_until_time / 1000000000;
uint64_t wait_until_nsec = wait_until_time % 1000000000;
@@ -1530,7 +1540,7 @@ int sps_pthread_mutex_timedlock(pthread_mutex_t *mutex, useconds_t dally_time,
timeoutTime.tv_nsec = wait_until_nsec;
int r = pthread_mutex_timedlock(mutex, &timeoutTime);
uint64_t et = get_absolute_time_in_ns() - start_time;
uint64_t et = get_realtime_in_ns() - start_time;
if ((debuglevel != 0) && (r != 0) && (debugmessage != NULL)) {
char errstr[1000];
+6 -3
View File
@@ -379,9 +379,12 @@ double flat_vol2attn(double vol, long max_db, long min_db);
double vol2attn(double vol, long max_db, long min_db);
// return a time in nanoseconds
// uint64_t get_absolute_time_in_fp(void); // obselete
uint64_t get_absolute_time_in_ns(void);
uint64_t get_monotonic_time_in_ns(void); // to try and get precise FPS values
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
// Not defined for macOS
uint64_t get_realtime_in_ns(void);
#endif
uint64_t get_absolute_time_in_ns(void); // monotonic_raw or monotonic
uint64_t get_monotonic_time_in_ns(void); // NTP-disciplined
// time at startup for debugging timing
extern uint64_t ns_time_at_startup, ns_time_at_last_debug_message;
+1 -1
View File
@@ -529,7 +529,7 @@ void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
(uint64_t)(1000000000 * config.missing_port_dacp_scan_interval_seconds);
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
uint64_t time_of_wakeup_ns = get_realtime_in_ns() + time_to_wait_for_wakeup_ns;
uint64_t sec = time_of_wakeup_ns / 1000000000;
uint64_t nsec = time_of_wakeup_ns % 1000000000;
#endif
+1 -1
View File
@@ -22,7 +22,7 @@
#define STORAGE_ID "/nqptp"
#define MAX_CLOCKS 32
#define NQPTP_SHM_STRUCTURES_VERSION 5
#define NQPTP_SHM_STRUCTURES_VERSION 6
#define NQPTP_CONTROL_PORT 9000
// the control port will accept a UDP packet with the first letter being:
+45 -30
View File
@@ -1299,7 +1299,7 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
time_to_wait_for_wakeup_ns /= 3; // two thirds of a packet time
#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
uint64_t time_of_wakeup_ns = local_time_now + time_to_wait_for_wakeup_ns;
uint64_t time_of_wakeup_ns = get_realtime_in_ns() + time_to_wait_for_wakeup_ns;
uint64_t sec = time_of_wakeup_ns / 1000000000;
uint64_t nsec = time_of_wakeup_ns % 1000000000;
@@ -1552,30 +1552,31 @@ int was_a_previous_column;
int *statistics_print_profile;
// these arrays specify which of the statistics specified by the statistics_item calls will actually
// be printed -- 1 means print, 0 means skip
// be printed -- 2 means print, 1 means print only in a debug mode, 0 means skip
// don't display output fps -- not sure how accurate it is (change item 14 and 17 to 1 to restore)
int ap1_synced_statistics_print_profile[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
int ap1_nosync_statistics_print_profile[] = {1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0};
int ap1_nodelay_statistics_print_profile[] = {0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0};
int ap1_synced_statistics_print_profile[] = {2, 2, 2, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1};
int ap1_nosync_statistics_print_profile[] = {2, 0, 0, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 0, 0, 1, 0};
int ap1_nodelay_statistics_print_profile[] = {0, 0, 0, 1, 2, 1, 1, 2, 0, 1, 1, 1, 1, 0, 0, 1, 0};
int ap2_realtime_synced_stream_statistics_print_profile[] = {1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 0, 1, 0, 0, 0};
int ap2_realtime_nosync_stream_statistics_print_profile[] = {1, 0, 0, 1, 1, 1, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0};
int ap2_realtime_nodelay_stream_statistics_print_profile[] = {0, 0, 0, 1, 1, 1, 1, 1, 0,
1, 1, 0, 0, 0, 0, 0, 0};
int ap2_realtime_synced_stream_statistics_print_profile[] = {2, 2, 2, 1, 2, 1, 1, 2, 1,
1, 1, 1, 1, 2, 2, 0, 0};
int ap2_realtime_nosync_stream_statistics_print_profile[] = {2, 0, 0, 1, 2, 1, 1, 2, 1,
1, 1, 1, 1, 0, 0, 0, 0};
int ap2_realtime_nodelay_stream_statistics_print_profile[] = {0, 0, 0, 1, 2, 1, 1, 2, 0,
1, 1, 1, 1, 0, 0, 0, 0};
// don't display output fps -- not sure how accurate it is (change item 14 1 to restore)
int ap2_buffered_synced_stream_statistics_print_profile[] = {1, 1, 1, 1, 0, 0, 0, 0, 1,
1, 1, 0, 0, 1, 0, 0, 0};
int ap2_buffered_nosync_stream_statistics_print_profile[] = {1, 0, 0, 1, 0, 0, 0, 0, 1,
int ap2_buffered_synced_stream_statistics_print_profile[] = {2, 2, 2, 1, 0, 0, 0, 0, 1,
1, 1, 0, 0, 2, 2, 0, 0};
int ap2_buffered_nosync_stream_statistics_print_profile[] = {2, 0, 0, 1, 0, 0, 0, 0, 1,
1, 1, 0, 0, 0, 0, 0, 0};
int ap2_buffered_nodelay_stream_statistics_print_profile[] = {0, 0, 0, 1, 0, 0, 0, 0, 0,
1, 1, 0, 0, 0, 0, 0, 0};
void statistics_item(const char *heading, const char *format, ...) {
if (statistics_print_profile[statistics_column] == 1) { // include this column?
if (((statistics_print_profile[statistics_column] == 1) && (debuglev != 0)) ||
(statistics_print_profile[statistics_column] == 2)) { // include this column?
if (was_a_previous_column != 0)
strcat(line_of_stats, " ");
if (statistics_row == 0) {
@@ -1624,10 +1625,10 @@ void player_thread_cleanup_handler(void *arg) {
int elapsedSec = rawSeconds % 60;
if (conn->frame_rate_valid)
inform("Connection %d: Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f, "
"output: %0.2f "
" Output: %0.2f (raw), %0.2f (corrected) "
"frames per second.",
conn->connection_number, elapsedHours, elapsedMin, elapsedSec, conn->input_frame_rate,
conn->frame_rate);
conn->raw_frame_rate, conn->corrected_frame_rate);
else
inform("Connection %d: Playback Stopped. Total playing time %02d:%02d:%02d. Input: %0.2f "
"frames per second.",
@@ -1727,7 +1728,8 @@ void *player_thread_func(void *arg) {
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
uint64_t previous_frames_played;
uint64_t previous_frames_played_time;
uint64_t previous_raw_measurement_time;
uint64_t previous_corrected_measurement_time;
int previous_frames_played_valid = 0;
// pthread_cleanup_push(player_thread_initial_cleanup_handler, arg);
@@ -1888,7 +1890,8 @@ void *player_thread_func(void *arg) {
conn->playstart = time(NULL);
conn->frame_rate = 0.0;
conn->raw_frame_rate = 0.0;
conn->corrected_frame_rate = 0.0;
conn->frame_rate_valid = 0;
conn->input_frame_rate = 0.0;
@@ -2335,10 +2338,12 @@ void *player_thread_func(void *arg) {
int stats_status = 0;
if ((config.output->delay) && (config.no_sync == 0) && (config.output->stats)) {
uint64_t frames_sent_for_play;
uint64_t measurement_time;
uint64_t raw_measurement_time;
uint64_t corrected_measurement_time;
uint64_t actual_delay;
stats_status =
config.output->stats(&measurement_time, &actual_delay, &frames_sent_for_play);
config.output->stats(&raw_measurement_time, &corrected_measurement_time,
&actual_delay, &frames_sent_for_play);
// debug(1,"status: %d, actual_delay: %" PRIu64 ", frames_sent_for_play: %" PRIu64 ",
// frames_played: %" PRIu64 ".", stats_status, actual_delay, frames_sent_for_play,
// frames_sent_for_play - actual_delay);
@@ -2347,9 +2352,13 @@ void *player_thread_func(void *arg) {
// last time the stats call was made. Thus, the frame rate should be valid.
if ((stats_status == 0) && (previous_frames_played_valid != 0)) {
uint64_t frames_played_in_this_interval = frames_played - previous_frames_played;
int64_t interval = measurement_time - previous_frames_played_time;
if (interval != 0) {
conn->frame_rate = (1e9 * frames_played_in_this_interval) / interval;
int64_t raw_interval = raw_measurement_time - previous_raw_measurement_time;
int64_t corrected_interval =
corrected_measurement_time - previous_corrected_measurement_time;
if (raw_interval != 0) {
conn->raw_frame_rate = (1e9 * frames_played_in_this_interval) / raw_interval;
conn->corrected_frame_rate =
(1e9 * frames_played_in_this_interval) / corrected_interval;
conn->frame_rate_valid = 1;
// debug(1,"frames_played_in_this_interval: %" PRIu64 ", interval: %" PRId64 ",
// rate: %f.",
@@ -2365,7 +2374,8 @@ void *player_thread_func(void *arg) {
if (stats_status != 0)
conn->frame_rate_valid = 0;
previous_frames_played = frames_played;
previous_frames_played_time = measurement_time;
previous_raw_measurement_time = raw_measurement_time;
previous_corrected_measurement_time = corrected_measurement_time;
previous_frames_played_valid = 1;
}
}
@@ -2411,15 +2421,19 @@ void *player_thread_func(void *arg) {
statistics_item("min buffers", "%*" PRIu32 "", 11, minimum_buffer_occupancy);
statistics_item("max buffers", "%*" PRIu32 "", 11, maximum_buffer_occupancy);
statistics_item("nominal fps", "%*.2f", 11, conn->remote_frame_rate);
statistics_item(" actual fps", "%*.2f", 11, conn->input_frame_rate);
if (conn->frame_rate_valid)
statistics_item(" output fps", "%*.2f", 11, conn->frame_rate);
else
statistics_item(" output fps", " N/A");
statistics_item("received fps", "%*.2f", 12, conn->input_frame_rate);
if (conn->frame_rate_valid) {
statistics_item("output fps (r)", "%*.2f", 14, conn->raw_frame_rate);
statistics_item("output fps (c)", "%*.2f", 14, conn->corrected_frame_rate);
} else {
statistics_item("output fps (r)", " N/A");
statistics_item("output fps (c)", " N/A");
}
statistics_item("source drift ppm", "%*.2f", 16,
(conn->local_to_remote_time_gradient - 1.0) * 1000000);
statistics_item("drift samples", "%*d", 13,
conn->local_to_remote_time_gradient_sample_count);
/*
statistics_item("estimated (unused) correction ppm", "%*.2f",
strlen("estimated (unused) correction ppm"),
(conn->frame_rate_valid != 0)
@@ -2429,6 +2443,7 @@ void *player_thread_func(void *arg) {
1000000) /
conn->frame_rate
: 0.0);
*/
statistics_row++;
inform(line_of_stats);
} while (statistics_row < 2);
+2 -1
View File
@@ -180,7 +180,8 @@ typedef struct {
stats_t *statistics;
// for holding the output rate information until printed out at the end of a session
double frame_rate;
double raw_frame_rate;
double corrected_frame_rate;
int frame_rate_valid;
// for holding input rate information until printed out at the end of a session
+1 -1
View File
@@ -1762,7 +1762,7 @@ void rtp_realtime_audio_cleanup_handler(__attribute__((unused)) void *arg) {
debug(2, "Realtime Audio Receiver Cleanup Start.");
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
close(conn->realtime_audio_socket);
debug(1, "Connection %d: closing realtime audio port %u", conn->local_realtime_audio_port);
debug(2, "Connection %d: closing realtime audio port %u", conn->local_realtime_audio_port);
conn->realtime_audio_socket = 0;
debug(2, "Realtime Audio Receiver Cleanup Done.");
}
+4
View File
@@ -583,7 +583,11 @@ void *player_watchdog_thread_code(void *arg) {
do {
usleep(2000000); // check every two seconds
// debug(3, "Connection %d: Check the thread is doing something...", conn->connection_number);
#ifdef CONFIG_AIRPLAY_2
if ((config.dont_check_timeout == 0) && (config.timeout != 0) && (conn->airplay_type == ap_1)) {
#else
if ((config.dont_check_timeout == 0) && (config.timeout != 0)) {
#endif
debug_mutex_lock(&conn->watchdog_mutex, 1000, 0);
uint64_t last_watchdog_bark_time = conn->watchdog_bark_time;
debug_mutex_unlock(&conn->watchdog_mutex, 0);