diff --git a/app/meson.build b/app/meson.build index b1cfb2b6..05c463e6 100644 --- a/app/meson.build +++ b/app/meson.build @@ -277,10 +277,6 @@ if get_option('buildtype') == 'debug' 'src/util/strbuf.c', 'src/util/term.c', ]], - ['test_clock', [ - 'tests/test_clock.c', - 'src/clock.c', - ]], ['test_control_msg_serialize', [ 'tests/test_control_msg_serialize.c', 'src/control_msg.c', diff --git a/app/src/clock.c b/app/src/clock.c index 3e1a794d..92989bfe 100644 --- a/app/src/clock.c +++ b/app/src/clock.c @@ -1,116 +1,36 @@ #include "clock.h" +#include + #include "util/log.h" #define SC_CLOCK_NDEBUG // comment to debug +#define SC_CLOCK_RANGE 32 + void sc_clock_init(struct sc_clock *clock) { - clock->count = 0; - clock->head = 0; - clock->left_sum.system = 0; - clock->left_sum.stream = 0; - clock->right_sum.system = 0; - clock->right_sum.stream = 0; -} - -// Estimate the affine function f(stream) = slope * stream + offset -static void -sc_clock_estimate(struct sc_clock *clock, - double *out_slope, sc_tick *out_offset) { - assert(clock->count); - - if (clock->count == 1) { - // If there is only 1 point, we can't compute a slope. Assume it is 1. - struct sc_clock_point *single_point = &clock->right_sum; - *out_slope = 1; - *out_offset = single_point->system - single_point->stream; - return; - } - - struct sc_clock_point left_avg = { - .system = clock->left_sum.system / (clock->count / 2), - .stream = clock->left_sum.stream / (clock->count / 2), - }; - struct sc_clock_point right_avg = { - .system = clock->right_sum.system / ((clock->count + 1) / 2), - .stream = clock->right_sum.stream / ((clock->count + 1) / 2), - }; - - double slope = (double) (right_avg.system - left_avg.system) - / (right_avg.stream - left_avg.stream); - - if (clock->count < SC_CLOCK_RANGE) { - /* The first frames are typically received and decoded with more delay - * than the others, causing a wrong slope estimation on start. To - * compensate, assume an initial slope of 1, then progressively use the - * estimated slope. */ - slope = (clock->count * slope + (SC_CLOCK_RANGE - clock->count)) - / SC_CLOCK_RANGE; - } - - struct sc_clock_point global_avg = { - .system = (clock->left_sum.system + clock->right_sum.system) - / clock->count, - .stream = (clock->left_sum.stream + clock->right_sum.stream) - / clock->count, - }; - - sc_tick offset = global_avg.system - (sc_tick) (global_avg.stream * slope); - - *out_slope = slope; - *out_offset = offset; + clock->range = 0; + clock->offset = 0; } void sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) { - struct sc_clock_point *point = &clock->points[clock->head]; - - if (clock->count == SC_CLOCK_RANGE || clock->count & 1) { - // One point passes from the right sum to the left sum - - unsigned mid; - if (clock->count == SC_CLOCK_RANGE) { - mid = (clock->head + SC_CLOCK_RANGE / 2) % SC_CLOCK_RANGE; - } else { - // Only for the first frames - mid = clock->count / 2; - } - - struct sc_clock_point *mid_point = &clock->points[mid]; - clock->left_sum.system += mid_point->system; - clock->left_sum.stream += mid_point->stream; - clock->right_sum.system -= mid_point->system; - clock->right_sum.stream -= mid_point->stream; + if (clock->range < SC_CLOCK_RANGE) { + ++clock->range; } - if (clock->count == SC_CLOCK_RANGE) { - // The current point overwrites the previous value in the circular - // array, update the left sum accordingly - clock->left_sum.system -= point->system; - clock->left_sum.stream -= point->stream; - } else { - ++clock->count; - } - - point->system = system; - point->stream = stream; - - clock->right_sum.system += system; - clock->right_sum.stream += stream; - - clock->head = (clock->head + 1) % SC_CLOCK_RANGE; - - // Update estimation - sc_clock_estimate(clock, &clock->slope, &clock->offset); + sc_tick offset = system - stream; + clock->offset = ((clock->range - 1) * clock->offset + offset) + / clock->range; #ifndef SC_CLOCK_NDEBUG - LOGD("Clock estimation: %f * pts + %" PRItick, clock->slope, clock->offset); + LOGD("Clock estimation: pts + %" PRItick, clock->offset); #endif } sc_tick sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream) { - assert(clock->count); // sc_clock_update() must have been called - return (sc_tick) (stream * clock->slope) + clock->offset; + assert(clock->range); // sc_clock_update() must have been called + return stream + clock->offset; } diff --git a/app/src/clock.h b/app/src/clock.h index 886d1f4d..0d34ab99 100644 --- a/app/src/clock.h +++ b/app/src/clock.h @@ -3,13 +3,8 @@ #include "common.h" -#include - #include "util/tick.h" -#define SC_CLOCK_RANGE 32 -static_assert(!(SC_CLOCK_RANGE & 1), "SC_CLOCK_RANGE must be even"); - struct sc_clock_point { sc_tick system; sc_tick stream; @@ -21,40 +16,18 @@ struct sc_clock_point { * * f(stream) = slope * stream + offset * - * To that end, it stores the SC_CLOCK_RANGE last clock points (the timestamps - * of a frame expressed both in stream time and system time) in a circular - * array. + * Theoretically, the slope encodes the drift between the device clock and the + * computer clock. It is expected to be very close to 1. * - * To estimate the slope, it splits the last SC_CLOCK_RANGE points into two - * sets of SC_CLOCK_RANGE/2 points, and computes their centroid ("average - * point"). The slope of the estimated affine function is that of the line - * passing through these two points. + * Since the clock is used to estimate very close points in the future (which + * are reestimated on every clock update, see delay_buffer), the error caused + * by clock drift is totally negligible, so it is better to assume that the + * slope is 1 than to estimate it (the estimation error would be larger). * - * To estimate the offset, it computes the centroid of all the SC_CLOCK_RANGE - * points. The resulting affine function passes by this centroid. - * - * With a circular array, the rolling sums (and average) are quick to compute. - * In practice, the estimation is stable and the evolution is smooth. + * Therefore, only the offset is estimated. */ struct sc_clock { - // Circular array - struct sc_clock_point points[SC_CLOCK_RANGE]; - - // Number of points in the array (count <= SC_CLOCK_RANGE) - unsigned count; - - // Index of the next point to write - unsigned head; - - // Sum of the first count/2 points - struct sc_clock_point left_sum; - - // Sum of the last (count+1)/2 points - struct sc_clock_point right_sum; - - // Estimated slope and offset - // (computed on sc_clock_update(), used by sc_clock_to_system_time()) - double slope; + unsigned range; sc_tick offset; }; diff --git a/app/src/delay_buffer.c b/app/src/delay_buffer.c index 9d4690a2..f6141b35 100644 --- a/app/src/delay_buffer.c +++ b/app/src/delay_buffer.c @@ -194,7 +194,7 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink, sc_clock_update(&db->clock, sc_tick_now(), pts); sc_cond_signal(&db->wait_cond); - if (db->first_frame_asap && db->clock.count == 1) { + if (db->first_frame_asap && db->clock.range == 1) { sc_mutex_unlock(&db->mutex); return sc_frame_source_sinks_push(&db->frame_source, frame); } diff --git a/app/tests/test_clock.c b/app/tests/test_clock.c deleted file mode 100644 index a88d5800..00000000 --- a/app/tests/test_clock.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "common.h" - -#include - -#include "clock.h" - -void test_small_rolling_sum(void) { - struct sc_clock clock; - sc_clock_init(&clock); - - assert(clock.count == 0); - assert(clock.left_sum.system == 0); - assert(clock.left_sum.stream == 0); - assert(clock.right_sum.system == 0); - assert(clock.right_sum.stream == 0); - - sc_clock_update(&clock, 2, 3); - assert(clock.count == 1); - assert(clock.left_sum.system == 0); - assert(clock.left_sum.stream == 0); - assert(clock.right_sum.system == 2); - assert(clock.right_sum.stream == 3); - - sc_clock_update(&clock, 10, 20); - assert(clock.count == 2); - assert(clock.left_sum.system == 2); - assert(clock.left_sum.stream == 3); - assert(clock.right_sum.system == 10); - assert(clock.right_sum.stream == 20); - - sc_clock_update(&clock, 40, 80); - assert(clock.count == 3); - assert(clock.left_sum.system == 2); - assert(clock.left_sum.stream == 3); - assert(clock.right_sum.system == 50); - assert(clock.right_sum.stream == 100); - - sc_clock_update(&clock, 400, 800); - assert(clock.count == 4); - assert(clock.left_sum.system == 12); - assert(clock.left_sum.stream == 23); - assert(clock.right_sum.system == 440); - assert(clock.right_sum.stream == 880); -} - -void test_large_rolling_sum(void) { - const unsigned half_range = SC_CLOCK_RANGE / 2; - - struct sc_clock clock1; - sc_clock_init(&clock1); - for (unsigned i = 0; i < 5 * half_range; ++i) { - sc_clock_update(&clock1, i, 2 * i + 1); - } - - struct sc_clock clock2; - sc_clock_init(&clock2); - for (unsigned i = 3 * half_range; i < 5 * half_range; ++i) { - sc_clock_update(&clock2, i, 2 * i + 1); - } - - assert(clock1.count == SC_CLOCK_RANGE); - assert(clock2.count == SC_CLOCK_RANGE); - - // The values before the last SC_CLOCK_RANGE points in clock1 should have - // no impact - assert(clock1.left_sum.system == clock2.left_sum.system); - assert(clock1.left_sum.stream == clock2.left_sum.stream); - assert(clock1.right_sum.system == clock2.right_sum.system); - assert(clock1.right_sum.stream == clock2.right_sum.stream); -} - -int main(int argc, char *argv[]) { - (void) argc; - (void) argv; - - test_small_rolling_sum(); - test_large_rolling_sum(); - return 0; -};