#include #include "../liana/common.h" #include "clock.h" #define RUNNING 0.0 #define PAUSED -DBL_MAX // @TODO: // Stop throwing away precision before we need to. // Assume calc_tick_offset() could give a negative result at any point. // Use a tick offset to set local buffer latency in a way that makes more sense. void camu_clock_init(struct camu_clock *clock, void (*callback)(void *, u8), void *userdata) { clock->callback = callback; clock->userdata = userdata; } static f64 calc_tick_offset(f64 tick, u64 now, u64 target) { if (now > target) { return tick - (now - target) / 1000000.0; } else { return tick + (target - now) / 1000000.0; } } static void offset_tick(struct camu_clock *clock, f64 diff) { diff += atomic_load(f64)(&clock->tick, AL_ATOMIC_ACQUIRE); atomic_store(f64)(&clock->tick, diff, AL_ATOMIC_RELEASE); } void camu_clock_set(struct camu_clock *clock, f64 base) { clock->base = base; atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED); atomic_store(f64)(&clock->pause, PAUSED, AL_ATOMIC_RELAXED); clock->paused_at = 0.0; atomic_store(f64)(&clock->last_pts, base, AL_ATOMIC_RELAXED); } void camu_clock_seek(struct camu_clock *clock, f64 base, u64 target) { clock->base = base; atomic_store(f64)(&clock->last_pts, base, AL_ATOMIC_RELAXED); if (clock->paused_at == -1.0) { // We are safe to directly edit the tick here. if (target == 0) { atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED); } else { f64 tick = nn_get_tick(); tick = calc_tick_offset(tick, nn_get_timestamp(), target); atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED); } } else { // Don't touch clock->pause here for the sake of sync. This means, // theoretically, the sink could still rely on a CLOCK_PAUSED callback // from an entry even after it was seeked. That would ultimately maintain sync but // the clock would report the old position in get_pts() before triggering CLOCK_PAUSED. // Likely confusing an entry's buffers and causing excessive catchup or delay. // We mitigate this sink-side by immediately switching to a potential target in // CLIENT_REMOVE_BUFFERS, which is always called before an entry is seeked. clock->paused_at = 0.0; } } // Seek to 0 but include the time it took to perform the seek. void camu_clock_loop(struct camu_clock *clock, f64 last_pts) { clock->base = 0.0; offset_tick(clock, last_pts - clock->base); } // If late, pause immediately and on resume() there will be a long delay. void camu_clock_pause(struct camu_clock *clock, u64 target) { al_assert(clock->paused_at == -1.0); f64 now = nn_get_tick(), tick = now; if (target > 0) { tick = calc_tick_offset(tick, nn_get_timestamp(), target); } atomic_store(f64)(&clock->pause, (tick > now) ? tick : -tick, AL_ATOMIC_RELAXED); clock->paused_at = tick; } // If late, this will be a catchup. void camu_clock_resume(struct camu_clock *clock, u64 target) { al_assert(clock->paused_at != -1.0); f64 tick = nn_get_tick(); if (target > 0) { tick = calc_tick_offset(tick, nn_get_timestamp(), target); } if (clock->paused_at == 0.0) { if (target == 0) { // target = 0 can never be synced. atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED); } else { atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED); } } else { f64 pause = atomic_load(f64)(&clock->pause, AL_ATOMIC_ACQUIRE); offset_tick(clock, tick - ((pause != PAUSED) ? -pause : clock->paused_at)); } atomic_store(f64)(&clock->pause, RUNNING, AL_ATOMIC_RELEASE); clock->paused_at = -1.0; } bool camu_clock_is_paused(struct camu_clock *clock) { return atomic_load(f64)(&clock->pause, AL_ATOMIC_RELAXED) < 0.0; } f64 camu_clock_get_base_pts(struct camu_clock *clock) { return clock->base; } f64 camu_clock_get_pts(struct camu_clock *clock, f64 latency, bool allow_set) { f64 pause = atomic_load(f64)(&clock->pause, AL_ATOMIC_RELAXED); if (pause < 0.0) return CAMU_PTS_PAUSED; f64 current = nn_get_tick(); f64 tick = atomic_load(f64)(&clock->tick, AL_ATOMIC_RELAXED); if (tick == -1.0) { if (allow_set) { tick = atomic_compare_and_swap(f64)(&clock->tick, -1.0, current); if (tick == -1.0) tick = current; } else { return CAMU_PTS_PAUSED; } } f64 pts = clock->base + (current - tick); bool signal_pause = false; if (pause > 0.0 && current > pause) { // pause > 0.0 = RUNNING or armed for pause. f64 paused_at = -current; pause = atomic_compare_and_swap(f64)(&clock->pause, pause, paused_at); if (pause != paused_at) { clock->callback(clock->userdata, CAMU_CLOCK_PAUSED); signal_pause = true; } } if (allow_set) { atomic_store(f64)(&clock->last_pts, pts, AL_ATOMIC_RELAXED); } return signal_pause ? CAMU_PTS_SIGNAL_PAUSE : pts + latency; } f64 camu_clock_get_last_pts(struct camu_clock *clock) { return atomic_load(f64)(&clock->last_pts, AL_ATOMIC_RELAXED); }