audio: the fast-forward measurement has one writer

The fast-forward speed measurement (last_flush_time and its two
averages) is taken by one thread: the submitter on the inline
pipeline, the main thread's frame end on the threaded pipeline and for
a callback core. Other threads wrote it too. Inline, the main thread's
frame end, pause and stop reset it while a core submitting from a
thread of its own was measuring. A callback core's audio thread reset
it beside the main thread's frame-end measurement. On the threaded
pipeline the producer read and reset it, and pipe_ff_frames was a
plain size_t added on the submitting thread and zeroed on the main one.
These are data races, and on 32-bit targets a 64-bit store can tear.

Now only the measuring thread writes the measurement. Any other thread
asks: one flag, stored only when not already up, which the measuring
thread takes before it next measures (audio_driver_ff_mult_sync(),
taken with fetch-and since exchange is not on every retro_atomic.h
backend). An idle frame costs one load. pipe_ff_frames is atomic: added
by the submitter, taken by what is there at the frame end, never stored
to zero. The producer knows the first frame of a hold from a flag of its
own instead of reading the main thread's measurement. A flush outside
fast-forward on the measuring thread still resets outright, which the
inline transport's source stamp overwrites as before.

This replaces the request-handler resets added for pause_fade in
0e35c138 with the same ask every other thread uses.

discrete_multichannel gains a lane with a core submitting from a
thread of its own while the main thread ends frames, holds and releases
fast-forward and pauses. Under ThreadSanitizer it reported nine races
on last_flush_time and pipe_ff_frames on the old code and none now; the
CI step builds it with TSan and runs that lane. Checks that read the
measurement straight after another thread's reset now accept the
pending ask.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-07 17:52:36 +01:00
1 parent 3b86a2126d
commit e24dafdc4d
7 files changed
+243 -66

No files matched your search

+13 -4
View File
@@ -325,7 +325,7 @@ jobs:
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./upmix_test
echo "[pass] upmix_test (ASan)"
- name: Build and run discrete_multichannel_test (plain, ASan + UBSan)
- name: Build and run discrete_multichannel_test (plain, ASan + UBSan, TSan)
shell: bash
working-directory: samples/audio/discrete_multichannel
run: |
@@ -341,17 +341,26 @@ jobs:
# 5.1 core reaches a Dolby Digital decoder as AC-3 bursts
# that decode back to its own channels. Includes the
# shipping audio_driver.c.
# The two builds in turn, the two runs at once: each run is
# one thread, so a runner's second core takes the other.
# The builds in turn, the runs at once: each run is one
# thread, so a runner's second core takes another.
# Under ThreadSanitizer only the lane with a core submitting
# from a thread of its own beside the main thread's frame
# ends, pauses and fast-forward changes: the fast-forward
# measurement is the submitter's, and no other thread may
# write it.
. "$GITHUB_WORKSPACE/.github/workflows/scripts/run-parallel.sh"
make clean && make -j$(nproc) all
mv discrete_multichannel_test discrete_multichannel_test_plain
make clean && make -j$(nproc) all SANITIZER=thread
mv discrete_multichannel_test discrete_multichannel_test_tsan
make clean && make -j$(nproc) all SANITIZER=address,undefined
par "discrete_multichannel_test" ./discrete_multichannel_test_plain
par "discrete_multichannel_test (ASan)" env ASAN_OPTIONS=detect_leaks=0 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./discrete_multichannel_test
par "discrete_multichannel_test (TSan: core thread)" env DM_ONLY=inlinethread \
TSAN_OPTIONS=halt_on_error=1 ./discrete_multichannel_test_tsan
par_wait
rm -f discrete_multichannel_test_plain
rm -f discrete_multichannel_test_plain discrete_multichannel_test_tsan
- name: Build and run embedded_alsa_test (the embedded ALSA driver, mock kernel device)
shell: bash
+109 -54
View File
@@ -2470,6 +2470,55 @@ static INLINE size_t audio_driver_input_bound(double ratio, size_t output_frames
#define AUDIO_OUTPUT_BOUND_CHECK(produced, bound) do {} while (0)
#endif
/* The fast-forward measurement (last_flush_time and the two averages)
* belongs to the one thread that takes it: the submitter on the inline
* pipeline, the main thread's frame end on the threaded one and for a
* callback core. That thread resets it outright; any other - the main
* thread's frame end, pause and stop inline, a callback core's audio
* thread, a producer submitting from a core's thread of its own - asks,
* and the measuring thread carries the reset out before it next
* measures. The ask is one flag, stored only when not already up, so an
* idle frame costs one load. */
static INLINE void audio_driver_ff_mult_reset(audio_driver_state_t *audio_st)
{
audio_st->last_flush_time = 0;
}
static INLINE void audio_driver_ff_mult_invalidate(audio_driver_state_t *audio_st)
{
#ifdef HAVE_THREADS
/* What the producer counted towards the next measurement goes too,
* taken by what is there: it may be adding beside this. */
size_t counted = retro_atomic_load_acquire_size(&audio_st->pipe_ff_frames);
if (counted)
retro_atomic_fetch_sub_size(&audio_st->pipe_ff_frames, counted);
#endif
if (!retro_atomic_load_relaxed_int(&audio_st->ff_reset_req))
retro_atomic_store_release_int(&audio_st->ff_reset_req, 1);
}
/* A flush outside fast-forward. Inline it runs on the thread that
* measures, which resets outright - and the inline transport, whose
* render flushes with fast-forward off, puts its source stamp back over
* that reset afterwards. A callback core flushes on the audio thread,
* which only asks. */
static INLINE void audio_driver_flush_ff_release(audio_driver_state_t *audio_st)
{
if (audio_st->callback.callback)
audio_driver_ff_mult_invalidate(audio_st);
else
audio_driver_ff_mult_reset(audio_st);
}
/* fetch-and with zero takes the flag: exchange is not on every backend
* of retro_atomic.h (see audio_driver_take_reinit_request()). */
static INLINE void audio_driver_ff_mult_sync(audio_driver_state_t *audio_st)
{
if ( retro_atomic_load_acquire_int(&audio_st->ff_reset_req)
&& retro_atomic_fetch_and_int(&audio_st->ff_reset_req, 0))
audio_driver_ff_mult_reset(audio_st);
}
static double audio_driver_fastforward_ratio_mult(
audio_driver_state_t *audio_st, size_t input_frames)
{
@@ -2479,6 +2528,8 @@ static double audio_driver_fastforward_ratio_mult(
const double expected_flush_delta =
(input_frames / audio_st->input * 1000000);
audio_driver_ff_mult_sync(audio_st);
/* An interval past the average's whole window at 1x is a stall,
* not a speed: seed again, as after a pause. */
if ( audio_st->last_flush_time > 0
@@ -2522,19 +2573,14 @@ static double audio_driver_fastforward_ratio_mult(
static double audio_driver_inline_ff_mult(audio_driver_state_t *audio_st,
size_t input_frames)
{
bool seed = !audio_st->last_flush_time;
double mult = audio_driver_fastforward_ratio_mult(audio_st, input_frames);
bool seed;
double mult;
audio_driver_ff_mult_sync(audio_st);
seed = !audio_st->last_flush_time;
mult = audio_driver_fastforward_ratio_mult(audio_st, input_frames);
return seed ? 1.0 : mult;
}
static INLINE void audio_driver_ff_mult_reset(audio_driver_state_t *audio_st)
{
audio_st->last_flush_time = 0;
#ifdef HAVE_THREADS
audio_st->pipe_ff_frames = 0;
#endif
}
#ifdef HAVE_THREADS
#define AUDIO_PIPE_FF_TRIM_MAX 0.08
#define AUDIO_PIPE_FF_TRIM_GAIN 0.5
@@ -2565,13 +2611,16 @@ static void audio_driver_ff_frame_end(audio_driver_state_t *audio_st)
#ifdef HAVE_THREADS
if (audio_st->pipe_threaded)
{
size_t frames = audio_st->pipe_ff_frames;
/* Taken by what was there, not stored to zero: the submitter
* may add beside this, from a thread of its own. */
size_t frames = retro_atomic_load_acquire_size(&audio_st->pipe_ff_frames);
if (audio_st->pipe_transport_follow)
audio_st->pipe_transport_follow |= AUDIO_TRANSPORT_UPDATE;
if (frames)
{
double mult = audio_driver_fastforward_ratio_mult(audio_st, frames);
audio_st->pipe_ff_frames = 0;
double mult;
retro_atomic_fetch_sub_size(&audio_st->pipe_ff_frames, frames);
mult = audio_driver_fastforward_ratio_mult(audio_st, frames);
if (audio_driver_pipe_ff_waits(audio_st))
mult = MIN(AUDIO_MAX_RATIO, MAX(AUDIO_MIN_RATIO,
mult / audio_driver_pipe_ff_trim(audio_st)));
@@ -2581,14 +2630,15 @@ static void audio_driver_ff_frame_end(audio_driver_state_t *audio_st)
}
}
#endif
/* Keep intervals across batched audio, but exclude released/paused gaps. */
if (audio_st->last_flush_time)
/* Keep intervals across batched audio, but exclude released/paused
* gaps. Inline the submitter measures, perhaps on a thread of its
* own, so this only asks. */
{
int snap = retro_atomic_load_acquire_int(&audio_st->runloop_snapshot);
if (!(snap & AUDIO_SNAP_FASTMOTION) || (snap & AUDIO_SNAP_PAUSED)
|| ((snap & AUDIO_SNAP_MENU_ALIVE) && (snap & AUDIO_SNAP_MENU_PAUSES)
&& (snap & AUDIO_SNAP_ALLOW_PAUSE)))
audio_driver_ff_mult_reset(audio_st);
audio_driver_ff_mult_invalidate(audio_st);
}
}
@@ -2608,9 +2658,9 @@ static void audio_driver_ff_frame_end(audio_driver_state_t *audio_st)
* Runs only when the snapshot carries the fastmotion bit, which
* publish withholds for callback cores unless the person opted in
* (audio_fastforward_callback), so the off state costs one load. The
* reset stores race the audio thread's own idle reset at fast-forward
* boundaries; both only ever store zeros and unity there, so the
* worst case is one stale EMA sample that the next frame corrects. */
* measurement is this thread's: the audio thread's idle reset at a
* fast-forward boundary only asks for it (audio_driver_ff_mult_invalidate()),
* and the next measurement here carries it out. */
static void audio_driver_ff_callback_frame_end(audio_driver_state_t *audio_st)
{
int snap = retro_atomic_load_acquire_int(&audio_st->runloop_snapshot);
@@ -2630,10 +2680,16 @@ static void audio_driver_ff_callback_frame_end(audio_driver_state_t *audio_st)
(int)(audio_driver_fastforward_ratio_mult(audio_st, frames)
* 65536.0));
}
else if (audio_st->last_flush_time)
else
{
audio_driver_ff_mult_reset(audio_st);
retro_atomic_store_release_int(&audio_st->pipe_ff_mult_q16, 65536);
/* This thread measures: it resets, taking the audio thread's ask
* with it, and publishes unity once on the way out. */
if (audio_st->last_flush_time)
{
audio_driver_ff_mult_sync(audio_st);
audio_driver_ff_mult_reset(audio_st);
retro_atomic_store_release_int(&audio_st->pipe_ff_mult_q16, 65536);
}
}
}
@@ -3800,7 +3856,7 @@ static void audio_driver_flush(audio_driver_state_t *audio_st,
/* Driver-side SRC needs the same speed multiplier as software SRC.
* In the threaded path this reads the producer's cadence estimate. */
if (!is_fastforward && !audio_st->pipe_threaded)
audio_driver_ff_mult_reset(audio_st);
audio_driver_flush_ff_release(audio_st);
if (is_fastforward && ff_speedup)
rate_adjust *= audio_driver_ff_mult(audio_st, frames);
@@ -3987,7 +4043,7 @@ static void audio_driver_flush(audio_driver_state_t *audio_st,
audio_st->src_ratio_curr = audio_st->src_ratio_orig
* audio_driver_sink_bias(audio_st);
if (!is_fastforward && !audio_st->pipe_threaded)
audio_driver_ff_mult_reset(audio_st);
audio_driver_flush_ff_release(audio_st);
i16_ratio = audio_driver_effective_ratio(audio_st, is_slowmotion,
slowmotion_ratio,
(is_fastforward && ff_speedup)
@@ -4374,7 +4430,7 @@ static void audio_driver_flush(audio_driver_state_t *audio_st,
audio_st->src_ratio_curr = audio_st->src_ratio_orig
* audio_driver_sink_bias(audio_st);
if (!is_fastforward && !audio_st->pipe_threaded)
audio_driver_ff_mult_reset(audio_st);
audio_driver_flush_ff_release(audio_st);
src_data.ratio = audio_driver_effective_ratio(audio_st,
is_slowmotion, slowmotion_ratio,
(is_fastforward && ff_speedup)
@@ -4824,14 +4880,19 @@ static bool audio_driver_inline_process(audio_driver_state_t *audio_st,
size_t left = frames;
size_t frame;
const uint8_t *source = (const uint8_t*)data;
retro_time_t old_time = audio_st->last_flush_time;
retro_time_t old_delta = audio_st->avg_flush_delta, source_time;
double old_expected = audio_st->avg_expected_delta;
retro_time_t old_time, old_delta, source_time;
double old_expected;
double duration = slowmotion ? slowmotion_ratio : 1.0;
uint64_t bits;
uint32_t tempo, cutoff;
bool supported, follow;
if (!t) return false;
/* An asked reset first, so a restore below cannot bring back what it
* cleared. */
audio_driver_ff_mult_sync(audio_st);
old_time = audio_st->last_flush_time;
old_delta = audio_st->avg_flush_delta;
old_expected = audio_st->avg_expected_delta;
follow = audio_driver_ff_follows(audio_st, (snap & AUDIO_SNAP_FF_SPEEDUP) != 0);
frame = t->channels * (floating ? sizeof(float) : sizeof(int16_t));
supported = floating == t->floating && !audio_st->extra.pending
@@ -5233,6 +5294,9 @@ bool audio_driver_init_internal(void *settings_data, bool audio_cb_inited)
retro_atomic_int_init(&audio_driver_st.pipe_stalled, 0);
retro_atomic_store_release_int(&audio_driver_st.pipe_ff_mult_q16, 65536);
audio_driver_ff_mult_reset(&audio_driver_st);
retro_atomic_int_init(&audio_driver_st.ff_reset_req, 0);
retro_atomic_size_init(&audio_driver_st.pipe_ff_frames, 0);
audio_driver_st.pipe_ff_held = false;
retro_atomic_int_init(&audio_driver_st.pipe_data_gen, 0);
retro_atomic_int_init(&audio_driver_st.pipe_wake, 0);
if ( !retro_eventcount_init(&audio_driver_st.pipe_space)
@@ -6262,15 +6326,9 @@ static void audio_driver_requests_apply(audio_driver_state_t *audio_st,
{
case 0:
/* Without the threaded pipeline the ramp's arming is the
* submitter's, as is the rest of a pause and a resume -
* and the flush interval it measures, which the pause
* must not be read as. */
* submitter's, as is the rest of a pause and a resume. */
if (!audio_st->pipe_threaded)
{
audio_st->fade_in_pending = false;
if (!audio_st->callback.callback)
audio_driver_ff_mult_reset(audio_st);
}
audio_driver_pause_tail(audio_st, audio_st->current_audio,
write_tail);
break;
@@ -6280,8 +6338,6 @@ static void audio_driver_requests_apply(audio_driver_state_t *audio_st,
&audio_st->pipe_resume_topup) != 0;
if (!audio_st->pipe_threaded && topup)
audio_st->fade_in_pending = true;
if (!audio_st->pipe_threaded && !audio_st->callback.callback)
audio_driver_ff_mult_reset(audio_st);
audio_driver_resume_reset(audio_st, topup);
}
break;
@@ -6438,16 +6494,9 @@ void audio_driver_pause_fade(bool paused)
const audio_driver_t *audio = audio_st->current_audio;
bool was_silenced;
/* The paused interval is not a flush interval. The threaded
* pipeline's producer and a callback core measure at the frame end,
* on this thread, so reset here. Inline, the submitting thread
* measures, and a core may submit from a thread of its own: the
* pause and resume requests below reset it there, under the claim
* (see audio_driver_requests_apply()). */
#ifdef HAVE_THREADS
if (audio_st->pipe_threaded || audio_st->callback.callback)
#endif
audio_driver_ff_mult_reset(audio_st);
/* The paused interval is not a flush interval. Asked of whichever
* thread measures (see audio_driver_ff_mult_invalidate()). */
audio_driver_ff_mult_invalidate(audio_st);
if ( !audio
|| !audio->write
@@ -7114,19 +7163,25 @@ static void audio_driver_submit_width(audio_driver_state_t *audio_st,
& AUDIO_SNAP_FF_SPEEDUP) != 0))
{
/* The first frame of a hold publishes the seed, so the tempo
* lands with this frame's source rather than a frame later. */
if (!audio_st->last_flush_time && !audio_st->pipe_ff_frames)
* lands with this frame's source rather than a frame later.
* Known from this thread's own state: the measurement is the
* main thread's. */
if (!audio_st->pipe_ff_held)
{
double ratio = audio_driver_snapshot_ffratio(audio_st);
audio_st->pipe_ff_held = true;
if (ratio > 1.0)
retro_atomic_store_release_int(&audio_st->pipe_ff_mult_q16,
(int)(65536.0 / ratio));
}
audio_st->pipe_ff_frames = frames > SIZE_MAX - audio_st->pipe_ff_frames
? SIZE_MAX : audio_st->pipe_ff_frames + frames;
retro_atomic_fetch_add_size(&audio_st->pipe_ff_frames, frames);
}
else
audio_driver_ff_mult_reset(audio_st);
{
/* Out of the hold: the measurement is asked to start again. */
audio_st->pipe_ff_held = false;
audio_driver_ff_mult_invalidate(audio_st);
}
if (len && (audio_st->pipe_transport_follow & AUDIO_TRANSPORT_UPDATE)
&& !audio_driver_transport_update_runloop(audio_st))
len = 0;
@@ -10488,7 +10543,7 @@ bool audio_driver_callback(void)
/* This callback owns inline source timing; the main frame must not reset it. */
if (core_paused || !(snap & AUDIO_SNAP_FASTMOTION))
audio_driver_ff_mult_reset(&audio_driver_st);
audio_driver_ff_mult_invalidate(&audio_driver_st);
if (!core_paused)
{
@@ -11558,7 +11613,7 @@ bool audio_driver_stop(void)
if (stopped)
{
/* The stopped interval is not part of the source cadence. */
audio_driver_ff_mult_reset(audio_st);
audio_driver_ff_mult_invalidate(audio_st);
#ifdef HAVE_THREADS
retro_atomic_store_release_int(&audio_st->pipe_ff_mult_q16, 65536);
#endif
+11 -2
View File
@@ -860,6 +860,10 @@ typedef struct
bool mixer_mute_enable;
#endif
/* A reset of the fast-forward measurement below, asked for by a
* thread that does not take it; the measuring thread carries it out
* (see audio_driver_ff_mult_sync()). */
retro_atomic_int_t ff_reset_req;
/* Sample the flush delta-time when fast forwarding to find the correct ratio. */
retro_time_t last_flush_time;
/* Exponential moving averages of the flush interval and of the
@@ -1096,8 +1100,13 @@ typedef struct
void *pipe_transport_output;
uint32_t pipe_transport_serial, pipe_transport_search;
unsigned pipe_transport_rate;
/* Producer-owned source count for the current fast-forward frame. */
size_t pipe_ff_frames;
/* Source frames counted for the current fast-forward frame: added
* by the thread that submits, taken by the main thread's frame end,
* which may not be the same thread. */
retro_atomic_size_t pipe_ff_frames;
/* Whether the submitter is inside a fast-forward hold; the
* submitter's own. */
bool pipe_ff_held;
/* A pause and a resume as ring positions, in the ring's own byte
* count, published by the main thread and read by the consumer. What
* the ring held at a pause is stale behind its tail: the consumer
@@ -20,6 +20,7 @@
#include <pthread.h>
#include <rthreads/rthreads.h>
#include <retro_timers.h>
#include <formats/rac3.h>
#include <formats/iec61937.h>
#include "fake_wasapi.h"
@@ -2892,6 +2893,90 @@ static void inline_hold_gap_case(bool pause, bool slow)
pause ? "pause" : slow ? "slow spell" : "stall", failures - before);
}
/* A core that submits from a thread of its own, without the threaded
* pipeline, while the main thread ends frames, releases and holds
* fast-forward and pauses. The submitting thread measures the hold's
* speed (last_flush_time and the averages), so no other thread may
* write them: the frame end, a pause and a stop ask for the reset, and
* the submitter carries it out before its next measurement. Judged by
* ThreadSanitizer; the checks below are what the reset must still do. */
static float core_thread_input[735 * 2];
static retro_atomic_int_t core_thread_frames, core_thread_done;
static void core_thread_hold(void *data)
{
int i;
(void)data;
for (i = 0; i < 600; i++)
{
audio_driver_sample_batch_float(core_thread_input, 735);
retro_atomic_fetch_add_int(&core_thread_frames, 1);
retro_sleep(0);
}
retro_atomic_store_release_int(&core_thread_done, 1);
}
static void inline_core_thread_case(void)
{
audio_driver_state_t *st = &audio_driver_st;
settings_t *settings = config_get_ptr();
unsigned before = failures;
unsigned step = 0;
sthread_t *th;
size_t f;
for (f = 0; f < 735; f++)
core_thread_input[2 * f] = core_thread_input[2 * f + 1]
= 0.3f * (float)sin(2 * M_PI * 441 * f / 44100.0);
CHECK(up(true, AUDIO_LAYOUT_STEREO, true), "core thread stand-up");
settings->bools.audio_time_stretch = true;
settings->bools.audio_time_stretch_lowpass = false;
settings->bools.audio_fastforward_speedup = false;
settings->floats.fastforward_ratio = 2.0f;
CHECK(audio_driver_transport_configure(settings) && st->inline_transport,
"core thread inline configuration");
fake_clock = false;
runloop_state_get_ptr()->flags = RUNLOOP_FLAG_FASTMOTION;
audio_driver_publish_runloop();
retro_atomic_int_init(&core_thread_frames, 0);
retro_atomic_int_init(&core_thread_done, 0);
th = sthread_create(core_thread_hold, NULL);
CHECK(th != NULL, "core thread started");
while (th && !retro_atomic_load_acquire_int(&core_thread_done))
{
/* Hold, release, hold, pause and resume, a frame end each. */
switch (step++ % 5)
{
case 0: case 2:
runloop_state_get_ptr()->flags = RUNLOOP_FLAG_FASTMOTION;
break;
case 1:
runloop_state_get_ptr()->flags = 0;
break;
case 3:
audio_driver_pause_fade(true);
runloop_state_get_ptr()->flags = RUNLOOP_FLAG_FASTMOTION
| RUNLOOP_FLAG_PAUSED;
break;
case 4:
runloop_state_get_ptr()->flags = RUNLOOP_FLAG_FASTMOTION;
audio_driver_pause_fade(false);
break;
}
audio_driver_frame_end();
retro_sleep(1);
}
if (th)
sthread_join(th);
CHECK(retro_atomic_load_acquire_int(&core_thread_frames) == 600,
"the core thread's audio was all taken");
CHECK(step > 5, "the main thread ended frames beside it");
runloop_state_get_ptr()->flags = 0;
settings->bools.audio_time_stretch = false;
settings->floats.fastforward_ratio = 0.0f;
audio_driver_publish_runloop();
audio_driver_deinit_internal(true);
printf("inline core thread: %u failures\n", failures - before);
}
#include "inline_wide.h"
#include "producer_spans.h"
@@ -2920,6 +3005,7 @@ int main(void)
RUN("inlinehold", inline_hold_gap_case(true, false));
RUN("inlinehold", inline_hold_gap_case(false, false));
RUN("inlinehold", inline_hold_gap_case(false, true));
RUN("inlinethread", inline_core_thread_case());
RUN("canonicalreserve", canonical_reserve_cases());
RUN("transportowner", transport_owner_cases());
RUN("transportdiscard", transport_discard_cases());
@@ -972,7 +972,10 @@ static void inline_format_cases(void)
}
st->last_flush_time = 123;
CHECK(audio_driver_stop() && audio_stretch_stream_quiescent(saved->stream), "rebound stop");
CHECK(!st->last_flush_time, "rebound stop retained speed cadence");
/* Asked of the measuring thread: see audio_driver_ff_mult_invalidate(). */
CHECK(!st->last_flush_time
|| retro_atomic_load_acquire_int(&st->ff_reset_req),
"rebound stop retained speed cadence");
CHECK(audio_driver_start(false), "rebound restart");
transport_fail_output = true;
audio_driver_set_core_float(!native);
+14 -4
View File
@@ -67,6 +67,15 @@ static void fresh(void)
fake_now = 1000000;
}
/* Whether the fast-forward measurement is reset, or asked to be: only
* the thread that measures clears it, at its next measurement (see
* audio_driver_ff_mult_invalidate()). */
static bool ff_measure_cleared(const audio_driver_state_t *st)
{
return !st->last_flush_time
|| retro_atomic_load_acquire_int(&st->ff_reset_req);
}
/* --- the measurement at the core's cadence ------------------------- */
static void test_inline_measures_speed(void)
@@ -534,7 +543,7 @@ static void test_producer_publishes_at_its_cadence(void)
/* Released: a 1.0x publish re-arms the seed. */
audio_driver_submit(&audio_driver_st, 1.0f, block, FRAMES * 2, false, false, false, true);
CHECK(audio_driver_st.last_flush_time == 0,
CHECK(ff_measure_cleared(&audio_driver_st),
"a publish outside fast-forward re-arms the seed");
retro_spsc_free(&audio_driver_st.pipe_ring);
@@ -931,7 +940,8 @@ static void test_inline_silent_boundaries(void)
* core's cadence resets in every mode - active fast-forward
* included - and reentry always starts at unity. */
CHECK((mode == 2 && !callback)
? st->last_flush_time == previous : st->last_flush_time == 0,
? st->last_flush_time == previous && !ff_measure_cleared(st)
: ff_measure_cleared(st),
"silent boundaries reset released/paused cadence and preserve active batching");
if (mode != 2 || callback)
CHECK(audio_driver_fastforward_ratio_mult(st, FRAMES) == 1.0,
@@ -947,7 +957,7 @@ static void test_inline_silent_boundaries(void)
retro_atomic_store_release_int(&st->runloop_snapshot, AUDIO_SNAP_FASTMOTION
| AUDIO_SNAP_MENU_ALIVE | AUDIO_SNAP_MENU_PAUSES | AUDIO_SNAP_ALLOW_PAUSE);
audio_driver_callback();
CHECK(!st->last_flush_time, "menu-paused callback discards idle cadence");
CHECK(ff_measure_cleared(st), "menu-paused callback discards idle cadence");
fresh();
runloop_state_get_ptr()->flags = 0;
}
@@ -1049,7 +1059,7 @@ static void test_stop_excludes_idle_gap(void)
"failed stop preserves the running cadence");
continue;
}
CHECK(!st->last_flush_time && !st->pipe_ff_frames,
CHECK(ff_measure_cleared(st) && !st->pipe_ff_frames,
"successful stop discards the old source cadence");
CHECK(retro_atomic_load_acquire_int(&st->pipe_ff_mult_q16) == 65536,
"restart cannot consume an old speed multiplier");
@@ -1055,7 +1055,12 @@ static void wrapper_restart(void)
parked_run(discard_parked, NULL);
if (cycle & 1)
{
if (!audio_driver_stop() || st->last_flush_time || st->pipe_ff_frames
/* The measurement is reset by the thread that takes it, or
* asked to be (see audio_driver_ff_mult_invalidate()). */
if (!audio_driver_stop()
|| (st->last_flush_time
&& !retro_atomic_load_acquire_int(&st->ff_reset_req))
|| retro_atomic_load_acquire_size(&st->pipe_ff_frames)
|| retro_atomic_load_acquire_int(&st->pipe_ff_mult_q16) != 65536)
{ fprintf(stderr, "frontend stop retained source cadence\n"); fixture_failures++; }
}