mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
audio_mixer: the windowed feeder's position and bound take no lock
audio_mixer_voice_buffer_tell() and audio_mixer_voice_set_avail() took the voice lock, which the mix holds across a voice's decode and resample: the feeder waited behind every decode, and the audio thread behind every feeder call. The mix publishes the decoder's byte position after each pass, and the feeder reads that. The feeder publishes the bound it wants, and the mix hands it to the decoder before its next decode. The decoder itself stays the mix's alone, under the voice lock as before. A bound is tagged with the lap - the stream's start and each rewind count one - its position was read in, and the mix drops it once the decoder has rewound: after a loop the window has let the old lap's pages go, and a bound worked out from the old position would admit reads of them. The locked version applied such a bound whenever the loop fell between the feeder's two calls. A bound set with no position read first is nobody's lap and stands across rewinds. A position is at most one mix pass old, a few kilobytes against the megabytes of lookahead both feeders keep. audio_mixer_loop_test case 3f covers the bound being applied by the mix, a stale one dropped and a fresh one taken; it fails on the locked code. audio_mixer_voice_threads_test runs a feeder thread against a looping stream voice under ThreadSanitizer.
This commit is contained in:
1 parent
1bdfd580b4
commit
3e8307ca8f
4 files changed
+347
-83
No files matched your search
@@ -239,6 +239,21 @@ struct audio_mixer_voice
|
||||
* volume holds the float's bits, gain the Q16.16 value. */
|
||||
retro_atomic_int_t volume;
|
||||
retro_atomic_int_t gain;
|
||||
/* What a windowed source's feeder exchanges with the mix, without
|
||||
* the lock. tell is the decoder's byte position as of the last mix
|
||||
* and lap counts the stream's start and its rewinds; avail_req is
|
||||
* the resident bound the feeder wants, applied before the next
|
||||
* decode. A bound carries the lap its position was read in and is
|
||||
* dropped once the decoder has rewound: the bytes it promised may
|
||||
* be gone. Lap 0 is a bound not derived from a position, -1 none. */
|
||||
retro_atomic_size_t tell;
|
||||
retro_atomic_int_t lap;
|
||||
retro_atomic_size_t avail_req;
|
||||
retro_atomic_int_t avail_lap;
|
||||
retro_atomic_int_t feeder_lap; /* the lap the feeder last read */
|
||||
size_t avail_done; /* the mix's: the bound it applied */
|
||||
int avail_done_lap;
|
||||
bool avail_done_valid;
|
||||
bool repeat;
|
||||
bool is_s16;
|
||||
#ifdef HAVE_THREADS
|
||||
@@ -1408,83 +1423,12 @@ void audio_mixer_sound_set_end_granule(audio_mixer_sound_t *sound,
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Compressed-byte read position of a stream voice's decoder within
|
||||
* its source buffer - the windowed-source feeder's input. Returns
|
||||
* 0 for anything that is not a live buffer-mode stream voice. Takes
|
||||
* the voice lock: safe against the mixing thread. */
|
||||
/* Raise a live stream voice's resident prefix - the windowed feeder's
|
||||
* output, the mirror of audio_mixer_voice_buffer_tell's input. Only
|
||||
* the WebM container arms act on it; a no-op for every other type and
|
||||
* for anything that is not a live buffer-mode stream voice. Takes the
|
||||
* voice lock: safe against the mixing thread. */
|
||||
void audio_mixer_voice_set_avail(audio_mixer_voice_t *voice, size_t avail)
|
||||
{
|
||||
#if (defined(HAVE_RWEBM) && (defined(HAVE_ROPUS) || defined(HAVE_RVORBIS))) \
|
||||
|| defined(HAVE_RAAC) || defined(HAVE_RFLAC) || defined(HAVE_RAC3)
|
||||
if (!voice)
|
||||
return;
|
||||
#ifdef AUDIO_MIXER_HAS_STREAM
|
||||
AUDIO_MIXER_LOCK(voice);
|
||||
switch (retro_atomic_load_relaxed_int(&voice->type))
|
||||
{
|
||||
#ifdef HAVE_RAC3
|
||||
case AUDIO_MIXER_TYPE_AC3:
|
||||
audio_transfer_set_avail(voice->types.stream.stream,
|
||||
AUDIO_TYPE_AC3, avail);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_RLPCM
|
||||
case AUDIO_MIXER_TYPE_LPCM:
|
||||
audio_transfer_set_avail(voice->types.stream.stream,
|
||||
AUDIO_TYPE_LPCM, avail);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_RVORBIS
|
||||
case AUDIO_MIXER_TYPE_OGG:
|
||||
audio_transfer_set_avail(voice->types.stream.stream,
|
||||
AUDIO_TYPE_VORBIS, avail);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_ROPUS
|
||||
case AUDIO_MIXER_TYPE_OPUS:
|
||||
audio_transfer_set_avail(voice->types.stream.stream,
|
||||
AUDIO_TYPE_OPUS, avail);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_RAAC
|
||||
/* The arm a windowed M4A actually lands on. Its absence meant
|
||||
* every feeder raise was accepted and dropped: the decoder kept
|
||||
* the bound it was given at add_stream, hit it a second or so
|
||||
* in, reported end of stream, and a looping voice restarted -
|
||||
* the same second of audio over and over. */
|
||||
case AUDIO_MIXER_TYPE_M4A:
|
||||
audio_transfer_set_avail(voice->types.stream.stream,
|
||||
AUDIO_TYPE_AAC, avail);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_RFLAC
|
||||
case AUDIO_MIXER_TYPE_FLAC:
|
||||
audio_transfer_set_avail(voice->types.stream.stream,
|
||||
AUDIO_TYPE_FLAC, avail);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
AUDIO_MIXER_UNLOCK(voice);
|
||||
#endif
|
||||
#else
|
||||
(void)voice; (void)avail;
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t audio_mixer_voice_buffer_tell(audio_mixer_voice_t *voice)
|
||||
/* The decoder's compressed-byte position within its source buffer.
|
||||
* The decoder is the mix's: call with the voice lock held. */
|
||||
static size_t audio_mixer_voice_tell_owned(audio_mixer_voice_t *voice)
|
||||
{
|
||||
size_t r = 0;
|
||||
if (!voice)
|
||||
return 0;
|
||||
#ifdef AUDIO_MIXER_HAS_STREAM
|
||||
AUDIO_MIXER_LOCK(voice);
|
||||
switch (retro_atomic_load_relaxed_int(&voice->type))
|
||||
{
|
||||
#ifdef HAVE_RWAV
|
||||
@@ -1532,11 +1476,155 @@ size_t audio_mixer_voice_buffer_tell(audio_mixer_voice_t *voice)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
AUDIO_MIXER_UNLOCK(voice);
|
||||
#else
|
||||
(void)voice;
|
||||
#endif
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Hand the decoder its resident bound. Only the windowed arms act on
|
||||
* it. Call with the voice lock held. */
|
||||
static void audio_mixer_voice_avail_owned(audio_mixer_voice_t *voice,
|
||||
size_t avail)
|
||||
{
|
||||
#if defined(AUDIO_MIXER_HAS_STREAM) \
|
||||
&& ((defined(HAVE_RWEBM) && (defined(HAVE_ROPUS) || defined(HAVE_RVORBIS))) \
|
||||
|| defined(HAVE_RAAC) || defined(HAVE_RFLAC) || defined(HAVE_RAC3))
|
||||
switch (retro_atomic_load_relaxed_int(&voice->type))
|
||||
{
|
||||
#ifdef HAVE_RAC3
|
||||
case AUDIO_MIXER_TYPE_AC3:
|
||||
audio_transfer_set_avail(voice->types.stream.stream,
|
||||
AUDIO_TYPE_AC3, avail);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_RLPCM
|
||||
case AUDIO_MIXER_TYPE_LPCM:
|
||||
audio_transfer_set_avail(voice->types.stream.stream,
|
||||
AUDIO_TYPE_LPCM, avail);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_RVORBIS
|
||||
case AUDIO_MIXER_TYPE_OGG:
|
||||
audio_transfer_set_avail(voice->types.stream.stream,
|
||||
AUDIO_TYPE_VORBIS, avail);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_ROPUS
|
||||
case AUDIO_MIXER_TYPE_OPUS:
|
||||
audio_transfer_set_avail(voice->types.stream.stream,
|
||||
AUDIO_TYPE_OPUS, avail);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_RAAC
|
||||
/* The arm a windowed M4A actually lands on. Its absence meant
|
||||
* every feeder raise was accepted and dropped: the decoder kept
|
||||
* the bound it was given at add_stream, hit it a second or so
|
||||
* in, reported end of stream, and a looping voice restarted -
|
||||
* the same second of audio over and over. */
|
||||
case AUDIO_MIXER_TYPE_M4A:
|
||||
audio_transfer_set_avail(voice->types.stream.stream,
|
||||
AUDIO_TYPE_AAC, avail);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_RFLAC
|
||||
case AUDIO_MIXER_TYPE_FLAC:
|
||||
audio_transfer_set_avail(voice->types.stream.stream,
|
||||
AUDIO_TYPE_FLAC, avail);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#else
|
||||
(void)voice; (void)avail;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int audio_mixer_lap_next(int lap)
|
||||
{
|
||||
return (lap > 0 && lap < 0x7fffffff) ? lap + 1 : 1;
|
||||
}
|
||||
|
||||
/* A voice being claimed: no bound asked for, a lap of its own. Before
|
||||
* the store that publishes the voice's type. */
|
||||
static void audio_mixer_feed_reset(audio_mixer_voice_t *voice)
|
||||
{
|
||||
retro_atomic_store_release_int(&voice->avail_lap, -1);
|
||||
retro_atomic_store_relaxed_int(&voice->feeder_lap, 0);
|
||||
retro_atomic_store_release_size(&voice->tell, 0);
|
||||
retro_atomic_store_release_int(&voice->lap, audio_mixer_lap_next(
|
||||
retro_atomic_load_relaxed_int(&voice->lap)));
|
||||
voice->avail_done_valid = false;
|
||||
}
|
||||
|
||||
/* Before a decode: take the bound the feeder asked for, if it is new
|
||||
* and its lap is still the decoder's. The feeder writes the lap, the
|
||||
* bound, the lap; a lap that changed across the read is a write in
|
||||
* progress, left for the next mix. */
|
||||
static void audio_mixer_feed_apply(audio_mixer_voice_t *voice)
|
||||
{
|
||||
size_t avail;
|
||||
int lap = retro_atomic_load_acquire_int(&voice->avail_lap);
|
||||
if (lap < 0)
|
||||
return;
|
||||
avail = retro_atomic_load_acquire_size(&voice->avail_req);
|
||||
if ( retro_atomic_load_acquire_int(&voice->avail_lap) != lap
|
||||
|| (lap && lap != retro_atomic_load_relaxed_int(&voice->lap)))
|
||||
return;
|
||||
if ( voice->avail_done_valid
|
||||
&& voice->avail_done == avail
|
||||
&& voice->avail_done_lap == lap)
|
||||
return;
|
||||
audio_mixer_voice_avail_owned(voice, avail);
|
||||
voice->avail_done = avail;
|
||||
voice->avail_done_lap = lap;
|
||||
voice->avail_done_valid = true;
|
||||
}
|
||||
|
||||
/* After a decode: where the decoder stands now. */
|
||||
static void audio_mixer_feed_publish(audio_mixer_voice_t *voice)
|
||||
{
|
||||
retro_atomic_store_release_size(&voice->tell,
|
||||
audio_mixer_voice_tell_owned(voice));
|
||||
}
|
||||
|
||||
/* The decoder went back to the top. Position before lap: the feeder
|
||||
* reads them the other way round, so it never pairs the old lap's
|
||||
* position with the new lap. A bound that is no position's stands
|
||||
* across the rewind and is handed over again. */
|
||||
static void audio_mixer_feed_rewound(audio_mixer_voice_t *voice)
|
||||
{
|
||||
audio_mixer_feed_publish(voice);
|
||||
retro_atomic_store_release_int(&voice->lap, audio_mixer_lap_next(
|
||||
retro_atomic_load_relaxed_int(&voice->lap)));
|
||||
voice->avail_done_valid = false;
|
||||
audio_mixer_feed_apply(voice);
|
||||
}
|
||||
|
||||
void audio_mixer_voice_set_avail(audio_mixer_voice_t *voice, size_t avail)
|
||||
{
|
||||
if ( !voice
|
||||
|| retro_atomic_load_acquire_int(&voice->type)
|
||||
== AUDIO_MIXER_TYPE_NONE)
|
||||
return;
|
||||
retro_atomic_store_release_int(&voice->avail_lap, -1);
|
||||
retro_atomic_store_release_size(&voice->avail_req, avail);
|
||||
retro_atomic_store_release_int(&voice->avail_lap,
|
||||
retro_atomic_load_relaxed_int(&voice->feeder_lap));
|
||||
}
|
||||
|
||||
size_t audio_mixer_voice_buffer_tell(audio_mixer_voice_t *voice)
|
||||
{
|
||||
if ( !voice
|
||||
|| retro_atomic_load_acquire_int(&voice->type)
|
||||
== AUDIO_MIXER_TYPE_NONE)
|
||||
return 0;
|
||||
retro_atomic_store_relaxed_int(&voice->feeder_lap,
|
||||
retro_atomic_load_acquire_int(&voice->lap));
|
||||
return retro_atomic_load_acquire_size(&voice->tell);
|
||||
}
|
||||
|
||||
void audio_mixer_destroy(audio_mixer_sound_t* sound)
|
||||
{
|
||||
void *handle = NULL;
|
||||
@@ -2069,6 +2157,7 @@ audio_mixer_voice_t* audio_mixer_play(audio_mixer_sound_t* sound,
|
||||
}
|
||||
|
||||
/* claim the voice, also helps with cleanup on error */
|
||||
audio_mixer_feed_reset(voice);
|
||||
retro_atomic_store_release_int(&voice->type, sound->type);
|
||||
|
||||
switch (sound->type)
|
||||
@@ -2149,6 +2238,7 @@ audio_mixer_voice_t* audio_mixer_play(audio_mixer_sound_t* sound,
|
||||
audio_mixer_float_bits(volume));
|
||||
voice->sound = sound;
|
||||
voice->stop_cb = stop_cb;
|
||||
audio_mixer_feed_publish(voice);
|
||||
AUDIO_MIXER_UNLOCK(voice);
|
||||
}
|
||||
else
|
||||
@@ -2196,6 +2286,7 @@ audio_mixer_voice_t* audio_mixer_play_s16(audio_mixer_sound_t* sound,
|
||||
* unlocked voice counts read the flag only once type says the
|
||||
* voice is claimed, so it has to be set by then. */
|
||||
voice->is_s16 = true;
|
||||
audio_mixer_feed_reset(voice);
|
||||
retro_atomic_store_release_int(&voice->type, sound->type);
|
||||
|
||||
switch (sound->type)
|
||||
@@ -2275,6 +2366,7 @@ audio_mixer_voice_t* audio_mixer_play_s16(audio_mixer_sound_t* sound,
|
||||
retro_atomic_store_relaxed_int(&voice->gain, (int)gain);
|
||||
voice->sound = sound;
|
||||
voice->stop_cb = stop_cb;
|
||||
audio_mixer_feed_publish(voice);
|
||||
AUDIO_MIXER_UNLOCK(voice);
|
||||
}
|
||||
else
|
||||
@@ -2553,6 +2645,7 @@ again:
|
||||
&& audio_transfer_seek(voice->types.stream.stream, type, 0))
|
||||
{
|
||||
rewound = 1;
|
||||
audio_mixer_feed_rewound(voice);
|
||||
|
||||
if (voice->stop_cb)
|
||||
voice->stop_cb(voice->sound, AUDIO_MIXER_SOUND_REPEATED);
|
||||
@@ -2691,6 +2784,7 @@ again:
|
||||
&& audio_transfer_seek(voice->types.stream.stream, type, 0))
|
||||
{
|
||||
rewound = 1;
|
||||
audio_mixer_feed_rewound(voice);
|
||||
if (voice->stop_cb)
|
||||
voice->stop_cb(voice->sound, AUDIO_MIXER_SOUND_REPEATED);
|
||||
goto again;
|
||||
@@ -2786,6 +2880,8 @@ void audio_mixer_mix(float* buffer, size_t num_frames,
|
||||
: audio_mixer_bits_float(
|
||||
retro_atomic_load_relaxed_int(&voice->volume));
|
||||
|
||||
audio_mixer_feed_apply(voice);
|
||||
|
||||
switch (retro_atomic_load_relaxed_int(&voice->type))
|
||||
{
|
||||
case AUDIO_MIXER_TYPE_WAV:
|
||||
@@ -2841,6 +2937,7 @@ void audio_mixer_mix(float* buffer, size_t num_frames,
|
||||
break;
|
||||
}
|
||||
|
||||
audio_mixer_feed_publish(voice);
|
||||
AUDIO_MIXER_UNLOCK(voice);
|
||||
}
|
||||
|
||||
@@ -2883,6 +2980,8 @@ void audio_mixer_mix_s16(int16_t* buffer, size_t num_frames,
|
||||
gain_q16 = (override) ? gain_override
|
||||
: (int32_t)retro_atomic_load_relaxed_int(&voice->gain);
|
||||
|
||||
audio_mixer_feed_apply(voice);
|
||||
|
||||
switch (retro_atomic_load_relaxed_int(&voice->type))
|
||||
{
|
||||
case AUDIO_MIXER_TYPE_FLAC:
|
||||
@@ -2938,6 +3037,7 @@ void audio_mixer_mix_s16(int16_t* buffer, size_t num_frames,
|
||||
break;
|
||||
}
|
||||
|
||||
audio_mixer_feed_publish(voice);
|
||||
AUDIO_MIXER_UNLOCK(voice);
|
||||
}
|
||||
/* No final clamp: audio_mixer_mix_*_s16 saturate as they accumulate. */
|
||||
|
||||
@@ -108,8 +108,10 @@ audio_mixer_sound_t* audio_mixer_load_weba(void *buffer, size_t size);
|
||||
audio_mixer_sound_t* audio_mixer_load_weba_avail(void *buffer, size_t size,
|
||||
size_t avail);
|
||||
|
||||
/* Compressed-byte read position of a stream voice's decoder (0 when
|
||||
* not a live buffer-mode stream voice). Thread-safe. */
|
||||
/* Compressed-byte read position of a stream voice's decoder as of the
|
||||
* last mix (0 when not a live buffer-mode stream voice). Takes no
|
||||
* lock; one thread per voice, the one that calls
|
||||
* audio_mixer_voice_set_avail. */
|
||||
size_t audio_mixer_voice_buffer_tell(audio_mixer_voice_t *voice);
|
||||
|
||||
void audio_mixer_destroy(audio_mixer_sound_t* sound);
|
||||
@@ -133,8 +135,12 @@ void audio_mixer_sound_set_end_granule(audio_mixer_sound_t *sound,
|
||||
* decoder's container header parse at open. 0 = fully resident. */
|
||||
void audio_mixer_sound_set_avail(audio_mixer_sound_t *sound, size_t avail);
|
||||
|
||||
/* Raise a live stream voice's resident prefix as the window slides.
|
||||
* Takes the voice lock, like audio_mixer_voice_buffer_tell. */
|
||||
/* Set a live stream voice's resident prefix as the window slides.
|
||||
* Takes no lock: the mix applies it before its next decode. A bound
|
||||
* set after audio_mixer_voice_buffer_tell belongs to the position
|
||||
* that call returned, and is dropped if the decoder has rewound since
|
||||
* - call both again. One set with no tell before it stands across
|
||||
* rewinds. */
|
||||
void audio_mixer_voice_set_avail(audio_mixer_voice_t *voice, size_t avail);
|
||||
|
||||
audio_mixer_voice_t* audio_mixer_play(audio_mixer_sound_t* sound,
|
||||
|
||||
@@ -423,6 +423,108 @@ static int mix_repeating(audio_mixer_sound_t *sound, int s16,
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_RLPCM
|
||||
/* Mix a voice for some calls and report the furthest its decoder got. */
|
||||
static size_t feed_run(audio_mixer_voice_t *voice, int calls)
|
||||
{
|
||||
float out[MIX_SAMPLES];
|
||||
size_t top = 0;
|
||||
int i;
|
||||
for (i = 0; i < calls; i++)
|
||||
{
|
||||
size_t at;
|
||||
memset(out, 0, sizeof(out));
|
||||
audio_mixer_mix(out, MIX_FRAMES, 1.0f, false);
|
||||
at = audio_mixer_voice_buffer_tell(voice);
|
||||
if (at > top)
|
||||
top = at;
|
||||
}
|
||||
return top;
|
||||
}
|
||||
|
||||
/* The windowed feeder's two calls, which take no lock: the position is
|
||||
* the one the mix published, and a bound is applied by the mix before
|
||||
* its next decode - unless the decoder has rewound since the position
|
||||
* the bound was worked out from, in which case the bytes it promises
|
||||
* may be gone and it is dropped. */
|
||||
static int check_feed_bound(const uint8_t *lpcm, size_t len)
|
||||
{
|
||||
const size_t b0 = len / 6, b1 = len / 2;
|
||||
audio_mixer_sound_t *snd = audio_mixer_load_lpcm(dup_bytes(lpcm, len), len);
|
||||
audio_mixer_voice_t *voice;
|
||||
size_t top;
|
||||
int ok = 1;
|
||||
|
||||
if (!snd)
|
||||
return 0;
|
||||
if (!(voice = audio_mixer_play(snd, true, 1.0f, NULL,
|
||||
RESAMPLER_QUALITY_DONTCARE, NULL)))
|
||||
{
|
||||
audio_mixer_destroy(snd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* a bound set with no position read stands on its own, across the
|
||||
* loops this run makes */
|
||||
audio_mixer_voice_set_avail(voice, b0);
|
||||
top = feed_run(voice, 60);
|
||||
if (top == 0 || top > b0)
|
||||
{
|
||||
printf(" the decoder reached %u under a bound of %u\n",
|
||||
(unsigned)top, (unsigned)b0);
|
||||
ok = 0;
|
||||
}
|
||||
|
||||
/* a bound for the position just read: taken */
|
||||
(void)audio_mixer_voice_buffer_tell(voice);
|
||||
audio_mixer_voice_set_avail(voice, b1);
|
||||
top = feed_run(voice, 120);
|
||||
if (top <= b0 || top > b1)
|
||||
{
|
||||
printf(" a raised bound of %u left the decoder at %u\n",
|
||||
(unsigned)b1, (unsigned)top);
|
||||
ok = 0;
|
||||
}
|
||||
|
||||
/* the voice loops after a position is read: a bound still worked
|
||||
* out from that position is dropped */
|
||||
(void)audio_mixer_voice_buffer_tell(voice);
|
||||
{
|
||||
float out[MIX_SAMPLES];
|
||||
int i;
|
||||
for (i = 0; i < 120; i++)
|
||||
{
|
||||
memset(out, 0, sizeof(out));
|
||||
audio_mixer_mix(out, MIX_FRAMES, 1.0f, false);
|
||||
}
|
||||
}
|
||||
audio_mixer_voice_set_avail(voice, len);
|
||||
top = feed_run(voice, 240);
|
||||
if (top > b1)
|
||||
{
|
||||
printf(" a bound from before the loop was applied after it (%u)\n",
|
||||
(unsigned)top);
|
||||
ok = 0;
|
||||
}
|
||||
|
||||
/* read again, set again: taken */
|
||||
(void)audio_mixer_voice_buffer_tell(voice);
|
||||
audio_mixer_voice_set_avail(voice, len);
|
||||
top = feed_run(voice, 240);
|
||||
if (top <= b1)
|
||||
{
|
||||
printf(" a fresh bound after the loop was not applied (%u)\n",
|
||||
(unsigned)top);
|
||||
ok = 0;
|
||||
}
|
||||
audio_mixer_stop(voice);
|
||||
audio_mixer_destroy(snd);
|
||||
if (ok)
|
||||
printf(" bounds applied by the mix, a stale one dropped\n");
|
||||
return ok;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int main(void)
|
||||
@@ -598,6 +700,18 @@ int main(void)
|
||||
}
|
||||
free(lpcm);
|
||||
}
|
||||
{
|
||||
size_t feed_len = 96000 * 4;
|
||||
uint8_t *feed = (uint8_t*)malloc(feed_len);
|
||||
size_t i;
|
||||
for (i = 0; i < feed_len; i++)
|
||||
feed[i] = (uint8_t)(i * 7 + 1);
|
||||
printf("3f. audio_mixer: a windowed feeder's position and bound, without the lock\n");
|
||||
current_case = "case 3f (feeder position and bound)";
|
||||
if (!check_feed_bound(feed, feed_len))
|
||||
fails++;
|
||||
free(feed);
|
||||
}
|
||||
#endif
|
||||
|
||||
printf("4. audio_mixer: a stream with no audio in it never becomes a voice\n");
|
||||
|
||||
+48
-4
@@ -20,7 +20,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* A voice's volume and gain, set by a control thread and read by a
|
||||
/* A stream voice's position read and its bound set by a feeder thread,
|
||||
* with no lock, while the audio thread mixes and loops the voice: every
|
||||
* position read must lie inside the stream.
|
||||
*
|
||||
* A voice's volume and gain, set by a control thread and read by a
|
||||
* second while the audio thread mixes the voice. Built under
|
||||
* ThreadSanitizer: a field two threads touch without ordering is a
|
||||
* reported race. Every read must give back one of the values set, and
|
||||
@@ -113,6 +117,25 @@ static void *reader_thread(void *arg)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static audio_mixer_voice_t *stream_voice;
|
||||
static size_t stream_size;
|
||||
static unsigned odd_tells;
|
||||
static unsigned tells;
|
||||
|
||||
static void *feeder_thread(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
while (retro_atomic_load_acquire_int(&mixing))
|
||||
{
|
||||
size_t at = audio_mixer_voice_buffer_tell(stream_voice);
|
||||
if (at > stream_size)
|
||||
odd_tells++;
|
||||
audio_mixer_voice_set_avail(stream_voice, at + 4096);
|
||||
tells++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *audio_thread(void *arg)
|
||||
{
|
||||
float buf[BLOCK * 2];
|
||||
@@ -136,10 +159,10 @@ static void *audio_thread(void *arg)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
pthread_t t, r;
|
||||
pthread_t t, r, f;
|
||||
size_t wav_size;
|
||||
unsigned char *wav;
|
||||
audio_mixer_sound_t *snd;
|
||||
unsigned char *wav, *wav2;
|
||||
audio_mixer_sound_t *snd, *snd2;
|
||||
unsigned i, bad = 0;
|
||||
|
||||
audio_mixer_init(MIX_RATE);
|
||||
@@ -153,9 +176,21 @@ int main(void)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* the same triangle as a silent stream voice, short enough to loop
|
||||
* many times under the feeder */
|
||||
if ( !(wav2 = make_wav(MIX_RATE / 100, MIX_RATE, &stream_size))
|
||||
|| !(snd2 = audio_mixer_load_wav_stream(wav2, stream_size))
|
||||
|| !(stream_voice = audio_mixer_play(snd2, true, 0.0f, "nearest",
|
||||
RESAMPLER_QUALITY_DONTCARE, NULL)))
|
||||
{
|
||||
printf("FAIL could not set up a repeating stream voice\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
retro_atomic_store_release_int(&mixing, 1);
|
||||
pthread_create(&t, NULL, audio_thread, NULL);
|
||||
pthread_create(&r, NULL, reader_thread, NULL);
|
||||
pthread_create(&f, NULL, feeder_thread, NULL);
|
||||
for (i = 0; i < ROUNDS; i++)
|
||||
{
|
||||
float v = (i & 1) ? 1.0f : 0.0f;
|
||||
@@ -171,12 +206,21 @@ int main(void)
|
||||
retro_atomic_store_release_int(&mixing, 0);
|
||||
pthread_join(t, NULL);
|
||||
pthread_join(r, NULL);
|
||||
pthread_join(f, NULL);
|
||||
|
||||
audio_mixer_stop(voice);
|
||||
audio_mixer_destroy(snd);
|
||||
audio_mixer_stop(stream_voice);
|
||||
audio_mixer_destroy(snd2);
|
||||
audio_mixer_done();
|
||||
free(wav);
|
||||
|
||||
if (odd_tells)
|
||||
{
|
||||
printf("FAIL %u of %u positions read lay outside the stream\n",
|
||||
odd_tells, tells);
|
||||
return 1;
|
||||
}
|
||||
if (bad || odd_reads || odd_blocks)
|
||||
{
|
||||
printf("FAIL %u reads gave back another volume, %u values never set were read, "
|
||||
|
||||
Reference in new issue
Block a user