summaryrefslogtreecommitdiff
path: root/src/buffer
diff options
context:
space:
mode:
Diffstat (limited to 'src/buffer')
-rw-r--r--src/buffer/audio.c63
-rw-r--r--src/buffer/clock.c118
-rw-r--r--src/buffer/clock.h10
-rw-r--r--src/buffer/video.c9
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);