#include #include #include "../liana/common.h" #include "clock.h" // clock->tick #define SET (-HUGE_VAL) // clock->pause #define RUNNING HUGE_VAL #define PAUSED (-HUGE_VAL) // clock->paused_at #define NOT_STARTED HUGE_VAL #define NOT_PAUSED (-HUGE_VAL) void camu_clock_init(struct camu_clock *clock, void (*callback)(void *, u8), void *userdata) { clock->callback = callback; clock->userdata = userdata; } static inline 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 inline 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, SET, AL_ATOMIC_RELAXED); atomic_store(f64)(&clock->pause, PAUSED, AL_ATOMIC_RELAXED); atomic_store(bool)(&clock->external_pause, false, AL_ATOMIC_RELAXED); clock->paused_at = NOT_STARTED; atomic_store(f64)(&clock->last_pts, base, AL_ATOMIC_RELAXED); } // Setting an offset will unconditionally break sync. Only used in local mode. void camu_clock_offset(struct camu_clock *clock, f64 offset) { clock->offset = offset; } 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 == NOT_PAUSED) { // We are safe to directly edit the tick here. if (target == 0) { atomic_store(f64)(&clock->tick, SET, 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 = NOT_STARTED; } } // If late, pause immediately and on resume() there will be a long delay. bool camu_clock_pause(struct camu_clock *clock, u64 target) { al_assert(clock->paused_at == NOT_PAUSED); f64 tick = atomic_load(f64)(&clock->tick, AL_ATOMIC_RELAXED); f64 current = nn_get_tick(), pause = current; if (target > 0) { pause = calc_tick_offset(pause, nn_get_timestamp(), target); } clock->paused_at = pause; if ((tick != SET && tick >= current) || pause <= current) { // Immediate pause. // This could be -0.0, meaning 0.0 has to be considered paused by get_pts(). atomic_store(f64)(&clock->pause, -pause, AL_ATOMIC_RELAXED); return true; } else { // Arm for pause. // This can never be 0.0. al_assert(pause != 0.0); atomic_store(f64)(&clock->pause, pause, AL_ATOMIC_RELAXED); return false; } } void camu_clock_external_pause(struct camu_clock *clock) { atomic_store(bool)(&clock->external_pause, true, AL_ATOMIC_RELAXED); } // If late, this will be a catchup. void camu_clock_resume(struct camu_clock *clock, u64 target) { al_assert(clock->paused_at != NOT_PAUSED); atomic_store(bool)(&clock->external_pause, false, AL_ATOMIC_RELAXED); f64 tick = nn_get_tick(); if (target > 0) { tick = calc_tick_offset(tick, nn_get_timestamp(), target); } if (clock->paused_at == NOT_STARTED) { if (target == 0) { // target = 0 can never be synced. atomic_store(f64)(&clock->tick, SET, AL_ATOMIC_RELAXED); } else { atomic_store(f64)(&clock->tick, tick, AL_ATOMIC_RELAXED); } } else { f64 pause = atomic_load(f64)(&clock->pause, AL_ATOMIC_ACQUIRE); al_assert(pause != PAUSED); /* Using pause instead of clock->paused_at is incorrect for sync. if (pause < 0.0) pause = -pause; offset_tick(clock, tick - pause); */ offset_tick(clock, tick - clock->paused_at); } clock->paused_at = NOT_PAUSED; atomic_store(f64)(&clock->pause, RUNNING, AL_ATOMIC_RELEASE); } 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, bool *armed_for_pause) { // Treat pause = 0.0 or -0.0 as paused. 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 == SET) { if (allow_set) { tick = atomic_compare_and_swap(f64)(&clock->tick, SET, current); if (tick == SET) tick = current; } else { return CAMU_PTS_PAUSED; } } f64 pts = (clock->base - clock->offset) + (current - tick); bool signal_pause = false; *armed_for_pause = pause != RUNNING || atomic_load(bool)(&clock->external_pause, AL_ATOMIC_RELAXED); if (pause > 0.0 && current > 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); }