diff options
| author | 2026-04-13 17:36:54 -0400 | |
|---|---|---|
| committer | 2026-04-13 17:36:54 -0400 | |
| commit | 4ceadc74f0086168fbc576032ba3e6f23af16e39 (patch) | |
| tree | a542703d78f2b29ac1a756fdceec78e40860bab0 /src/buffer | |
| parent | 77b54c35bf9587450cd636e0d7df37e190e28bfb (diff) | |
| download | camu-4ceadc74f0086168fbc576032ba3e6f23af16e39.tar.gz camu-4ceadc74f0086168fbc576032ba3e6f23af16e39.tar.bz2 camu-4ceadc74f0086168fbc576032ba3e6f23af16e39.zip | |
Changes that went uncommitted for too long
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src/buffer')
| -rw-r--r-- | src/buffer/audio.c | 63 | ||||
| -rw-r--r-- | src/buffer/clock.c | 118 | ||||
| -rw-r--r-- | src/buffer/clock.h | 10 | ||||
| -rw-r--r-- | src/buffer/video.c | 9 |
4 files changed, 125 insertions, 75 deletions
diff --git a/src/buffer/audio.c b/src/buffer/audio.c index f066f18..7c10dda 100644 --- a/src/buffer/audio.c +++ b/src/buffer/audio.c @@ -15,15 +15,21 @@ #define BUFFER_MARK_BUFFERED 0.35 #ifdef CAMU_AUDIO_BUFFER_FADE -#define FADE_STEP(fmt, down) ((down ? -2.5f : 2.5f) / (fmt)->sample_rate) -#define FADE_MIN 0.175f +#define FADE_STEP(fmt, down) ((down ? -4.75f : 2.25f) / (fmt)->sample_rate) +#define FADE_MIN 0.25f +#define FADE_TAIL 1 #endif enum { - PAUSE_PAUSED = 0, + // Resync (skip/delay) on read(), skip fade and signal BUFFER_PAUSED on pause. + PAUSE_SYNC = 0, + // Already signaled BUFFER_PAUSED, resync on resume. + PAUSE_PAUSED, #ifdef CAMU_AUDIO_BUFFER_FADE + // Fade out but don't consume any data from the buffer. PAUSE_FADING, - PAUSE_FADING_COMPLETE, + // Return silence fade.tail times then pause. + PAUSE_FADE_COMPLETE, #endif PAUSE_PLAYING }; @@ -31,7 +37,7 @@ enum { static void reset_buffer_state(struct camu_audio_buffer *buf) { atomic_store(f64)(&buf->pts, -1.0, AL_ATOMIC_RELAXED); - buf->pause = PAUSE_PAUSED; + buf->pause = PAUSE_SYNC; atomic_store(u32)(&buf->unpause, 0, AL_ATOMIC_RELAXED); buf->logged_delay = false; atomic_store(u32)(&buf->volume.set, 0, AL_ATOMIC_RELAXED); @@ -125,6 +131,7 @@ static bool push_internal(struct camu_audio_buffer *buf, f64 pts, u8 **data, s32 if (data && sample_count > 0) { f64 duration = camu_audio_format_samples_to_sec(&buf->fmt.in, sample_count); if (frame_is_late(buf->clock, base_pts, pts, duration)) { + log_trace("Discarding late frame with PTS %f.", pts); return true; } if (buf->fmt.resampler_needed) { @@ -281,20 +288,25 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif ptrdiff_t ret, signal = req; f64 base_pts = atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); - bool set_clock = atomic_load(bool)(&buf->no_video, AL_ATOMIC_RELAXED); - f64 pts = camu_clock_get_pts(buf->clock, buf->latency, set_clock); + bool allow_set = atomic_load(bool)(&buf->no_video, AL_ATOMIC_RELAXED); + bool armed_for_pause = false; + f64 pts = camu_clock_get_pts(buf->clock, buf->latency, allow_set, &armed_for_pause); if (pts == CAMU_PTS_SIGNAL_PAUSE) { return 0; - } else if (pts == CAMU_PTS_PAUSED) { + } else if (pts == CAMU_PTS_PAUSED || armed_for_pause) { #ifdef CAMU_AUDIO_BUFFER_FADE + if (buf->pause == PAUSE_SYNC) { + buf->pause = PAUSE_FADE_COMPLETE; + buf->fade.tail = FADE_TAIL; + buf->logged_delay = false; + } if (buf->pause == PAUSE_PLAYING) { buf->pause = PAUSE_FADING; // fade offset should only ever be read when pause = FADING. buf->fade.offset = 0; - } else if (buf->pause == PAUSE_PAUSED || buf->pause == PAUSE_FADING_COMPLETE || - (buf->fade.volume == 0.f)) { + } else if (buf->pause == PAUSE_PAUSED || buf->pause == PAUSE_FADE_COMPLETE || buf->fade.volume == 0.f) { al_memset(data, 0, req); - if (buf->pause == PAUSE_FADING_COMPLETE) { + if (buf->pause == PAUSE_FADE_COMPLETE) { if (!--buf->fade.tail) { buf->callback(buf->userdata, CAMU_BUFFER_PAUSED); buf->pause = PAUSE_PAUSED; @@ -302,19 +314,24 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif } else if (buf->pause != PAUSE_PAUSED) { // Don't signal PAUSED until the next read to ensure at least 1 silent // frame is included in the fade out. - buf->pause = PAUSE_FADING_COMPLETE; + buf->pause = PAUSE_FADE_COMPLETE; // How many frames of silence to append after fading. - buf->fade.tail = 1; + buf->fade.tail = FADE_TAIL; } return req; } - } else if (buf->pause == PAUSE_FADING || buf->pause == PAUSE_FADING_COMPLETE) { + } else if (buf->pause == PAUSE_FADING || buf->pause == PAUSE_FADE_COMPLETE) { // If unpaused during a fade out, resume immediately to not break the stream. - buf->pause = PAUSE_PLAYING; - // Though, we will still jump back (possible pop) unless we are ignoring sync. + // If we're ignoring desync, we can move the buffer up the the current + // fade position so there's no break _or_ jump in the stream. if (buf->ignore_desync) { + buf->pause = PAUSE_PLAYING; ret = al_ring_buffer_discard(&buf->rb, buf->fade.offset); - base_pts += camu_audio_format_bytes_to_sec(fmt, ret); + f64 skip = camu_audio_format_bytes_to_sec(fmt, buf->fade.offset); + base_pts += skip; + log_info("Resuming at fade position, skipping %fs of audio (%zd bytes).", skip, ret); + } else { + buf->pause = PAUSE_SYNC; } #else // No fade and clock paused. al_memset(data, 0, req); @@ -336,8 +353,8 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif if (!buf->ignore_desync && atomic_load(u32)(&buf->unpause, AL_ATOMIC_ACQUIRE) > 0) { // Queuing multiple resyncs before resuming the stream will cause pops! - log_debug("Forcing resync."); - buf->pause = PAUSE_PAUSED; + log_info("Forcing resync."); + buf->pause = PAUSE_SYNC; atomic_sub(u32)(&buf->unpause, 1, AL_ATOMIC_RELEASE); } @@ -353,13 +370,13 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif // Unpause and possibly attempt syncing to the clock. // PAUSE_PAUSED signifies that the last read was silence, meaning we can skip around // in the buffer without worrying about pops. - if (UNLIKELY(buf->pause == PAUSE_PAUSED)) { + if (UNLIKELY(buf->pause == PAUSE_SYNC || buf->pause == PAUSE_PAUSED)) { if (!buf->ignore_desync) { pts -= base_pts; if (pts > 0.0) { // Skip. ret = MIN((ptrdiff_t)camu_audio_format_sec_to_bytes(fmt, pts), have); - log_info("Skipping %fs of audio (%zd bytes).", pts, ret); ret = al_ring_buffer_discard(&buf->rb, ret); + log_info("Skipping %fs of audio (%zd bytes).", pts, ret); have -= ret; base_pts += camu_audio_format_bytes_to_sec(fmt, ret); // Could go on to underrun. @@ -367,7 +384,7 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif pts = -pts; ret = MIN((ptrdiff_t)camu_audio_format_sec_to_bytes(fmt, pts), req); if (!buf->logged_delay) { - log_info("Delaying audio by %fs.", pts); + log_info("Delaying audio by %fs (%zd bytes).", pts, ret); buf->logged_delay = true; } al_memset(data, 0, ret); @@ -410,7 +427,7 @@ ptrdiff_t camu_audio_buffer_read(struct camu_audio_buffer *buf, u8 *data, ptrdif // If we have data by the next read(), try to skip ahead to maintain sync. // This might exacerbate the underrun issue but an underrun is already // unexpected behavior, trying to stay in sync comes first. - buf->pause = PAUSE_PAUSED; + buf->pause = PAUSE_SYNC; } // If flow = SIGNALED, we are waiting to be removed or reset. } diff --git a/src/buffer/clock.c b/src/buffer/clock.c index 356ac56..86d6739 100644 --- a/src/buffer/clock.c +++ b/src/buffer/clock.c @@ -1,16 +1,20 @@ -#include <nnwt/thread.h> +#include <al/math.h> +#include <nnwt/time.h> #include "../liana/common.h" #include "clock.h" -#define RUNNING 0.0 -#define PAUSED -DBL_MAX +// clock->tick +#define SET (-HUGE_VAL) -// @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. +// 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) { @@ -18,16 +22,16 @@ void camu_clock_init(struct camu_clock *clock, void (*callback)(void *, u8), voi clock->userdata = userdata; } -static f64 calc_tick_offset(f64 tick, u64 now, u64 target) +static inline f64 calc_tick_offset(f64 tick, u64 now, u64 target) { if (now > target) { - return tick - (now - target) / 1000000.0; + return tick - ((now - target) / 1000000.0); } else { - return tick + (target - now) / 1000000.0; + return tick + ((target - now) / 1000000.0); } } -static void offset_tick(struct camu_clock *clock, f64 diff) +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); @@ -36,20 +40,27 @@ static void offset_tick(struct camu_clock *clock, f64 diff) 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->tick, SET, AL_ATOMIC_RELAXED); atomic_store(f64)(&clock->pause, PAUSED, AL_ATOMIC_RELAXED); - clock->paused_at = 0.0; + 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 == -1.0) { + if (clock->paused_at == NOT_PAUSED) { // We are safe to directly edit the tick here. if (target == 0) { - atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED); + atomic_store(f64)(&clock->tick, SET, AL_ATOMIC_RELAXED); } else { f64 tick = nn_get_tick(); tick = calc_tick_offset(tick, nn_get_timestamp(), target); @@ -63,62 +74,73 @@ void camu_clock_seek(struct camu_clock *clock, f64 base, u64 target) // 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; + clock->paused_at = NOT_STARTED; } } -// 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) +bool camu_clock_pause(struct camu_clock *clock, u64 target) { - al_assert(clock->paused_at == -1.0); + al_assert(clock->paused_at == NOT_PAUSED); - f64 now = nn_get_tick(), tick = now; + f64 tick = atomic_load(f64)(&clock->tick, AL_ATOMIC_RELAXED); + + f64 current = nn_get_tick(), pause = current; if (target > 0) { - tick = calc_tick_offset(tick, nn_get_timestamp(), target); + pause = calc_tick_offset(pause, nn_get_timestamp(), target); } - atomic_store(f64)(&clock->pause, (tick > now) ? tick : -tick, AL_ATOMIC_RELAXED); + clock->paused_at = pause; - clock->paused_at = tick; + 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 != -1.0); + 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 == 0.0) { + if (clock->paused_at == NOT_STARTED) { if (target == 0) { // target = 0 can never be synced. - atomic_store(f64)(&clock->tick, -1.0, AL_ATOMIC_RELAXED); + 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); - offset_tick(clock, tick - ((pause != PAUSED) ? -pause : clock->paused_at)); + 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); } - atomic_store(f64)(&clock->pause, RUNNING, AL_ATOMIC_RELEASE); - - clock->paused_at = -1.0; -} + clock->paused_at = NOT_PAUSED; -bool camu_clock_is_paused(struct camu_clock *clock) -{ - return atomic_load(f64)(&clock->pause, AL_ATOMIC_RELAXED) < 0.0; + atomic_store(f64)(&clock->pause, RUNNING, AL_ATOMIC_RELEASE); } f64 camu_clock_get_base_pts(struct camu_clock *clock) @@ -126,26 +148,28 @@ 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 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; + 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 (tick == SET) { if (allow_set) { - tick = atomic_compare_and_swap(f64)(&clock->tick, -1.0, current); - if (tick == -1.0) tick = current; + tick = atomic_compare_and_swap(f64)(&clock->tick, SET, current); + if (tick == SET) tick = current; } else { return CAMU_PTS_PAUSED; } } - f64 pts = clock->base + (current - tick); + f64 pts = (clock->base - clock->offset) + (current - tick); bool signal_pause = false; - if (pause > 0.0 && current > pause) { // pause > 0.0 = RUNNING or armed for pause. + *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) { diff --git a/src/buffer/clock.h b/src/buffer/clock.h index 8324b6a..eb3dcaf 100644 --- a/src/buffer/clock.h +++ b/src/buffer/clock.h @@ -33,8 +33,10 @@ enum { struct camu_clock { f64 base; + f64 offset; atomic(f64) tick; atomic(f64) pause; + atomic(bool) external_pause; f64 paused_at; atomic(f64) last_pts; void (*callback)(void *, u8); @@ -43,14 +45,14 @@ struct camu_clock { void camu_clock_init(struct camu_clock *clock, void (*callback)(void *, u8), void *userdata); void camu_clock_set(struct camu_clock *clock, f64 base); +void camu_clock_offset(struct camu_clock *clock, f64 offset); void camu_clock_seek(struct camu_clock *clock, f64 base, u64 target); -void camu_clock_loop(struct camu_clock *clock, f64 last_pts); -void camu_clock_pause(struct camu_clock *clock, u64 target); +bool camu_clock_pause(struct camu_clock *clock, u64 target); +void camu_clock_external_pause(struct camu_clock *clock); void camu_clock_resume(struct camu_clock *clock, u64 target); -bool camu_clock_is_paused(struct camu_clock *clock); f64 camu_clock_get_base_pts(struct camu_clock *clock); -f64 camu_clock_get_pts(struct camu_clock *clock, f64 latency, bool allow_set); +f64 camu_clock_get_pts(struct camu_clock *clock, f64 latency, bool allow_set, bool *armed_for_pause); f64 camu_clock_get_last_pts(struct camu_clock *clock); diff --git a/src/buffer/video.c b/src/buffer/video.c index 93356ac..5a469a4 100644 --- a/src/buffer/video.c +++ b/src/buffer/video.c @@ -15,10 +15,17 @@ // MARK_LOW is considered directly after reading a frame, so in other words, // it will trigger at the point where there is about (LOW+1) frames left. Even at // something like 240fps that's still ~(4.16*(LOW+1))ms to uncork and produce a new frame. +#ifdef CAMU_HUGE_VIDEO_BUFFER +#define BUFFER_MARK_LOW 24 +#define BUFFER_MARK_BUFFERED 36 // Must be >1. +#define BUFFER_MARK_HIGH 48 +#define BUFFER_MARK_RESET (BUFFER_MARK_HIGH * 2) +#else #define BUFFER_MARK_LOW 4 #define BUFFER_MARK_BUFFERED 6 // Must be >1. #define BUFFER_MARK_HIGH 8 #define BUFFER_MARK_RESET (BUFFER_MARK_HIGH * 2) +#endif bool camu_video_buffer_init(struct camu_video_buffer *buf, struct camu_clock *clock) { @@ -125,7 +132,7 @@ static bool push_av_frame_internal(struct camu_video_buffer *buf, AVFrame *frame f64 base_pts = atomic_load(f64)(&buf->pts, AL_ATOMIC_ACQUIRE); f64 duration = camu_ff_frame_duration(frame) * av_q2d(stream->time_base); if (!buf->single_frame && frame_is_late(buf->clock, base_pts, pts, duration)) { - log_debug("Discarding late frame with PTS %f.", pts); + log_trace("Discarding late frame with PTS %f.", pts); return false; } if (base_pts == -1.0) atomic_store(f64)(&buf->pts, pts, AL_ATOMIC_RELEASE); |