audio_mixer: fix four defects the windowed preview path exposed

ef19c2f handed the mixer a windowed container.  Four things in the
mixer could not take one, and between them they produced a preview
that looped the same second forever and a SIGSEGV on WebM.

1. audio_mixer_voice_set_avail had arms for OGG and OPUS only.  A
   windowed M4A voice therefore accepted every feeder raise and
   dropped it: the decoder kept the bound it was given at add_stream,
   reached it a second or so in, reported end of stream, and a looping
   voice restarted.  That is the repeat.  M4A and FLAC arms added and
   the guard widened - it required HAVE_RWEBM with Opus or Vorbis, so
   the whole function compiled to nothing in an AAC-only build.

2. audio_mixer_load_weba sniffed the codec with the file's whole
   length.  It runs before the sound exists, so sound_set_avail cannot
   have been applied, and on a windowed buffer the segment walk went
   straight off the committed head:

     #0 rwebm_open_memory_avail
     #1 audio_transfer_webm_audio_type
     #2 audio_mixer_load_weba
     #3 audio_driver_mixer_add_stream

   Added audio_mixer_load_weba_avail, which bounds the sniff; the
   plain form is now a wrapper passing 0, so no other caller changes.

3. The load_* API took int32_t size and the sound stored it as
   unsigned.  A 7,817,178,301-byte container arrived as -772,756,291
   and was stored as 3,522,211,005 - the demuxer then treated every
   sample past 3.5 GB as out of range, which truncates the stream
   regardless of what the feeder does.  Both widened to size_t.

4. sound_set_avail was applied under #ifdef HAVE_ROPUS, so a build
   without Opus dropped the resident bound entirely and opened the
   decoder against the whole file with only the head committed.  The
   bound is not Opus-specific; it now applies unconditionally, with
   only the end-granule injection left under HAVE_ROPUS.

On the testing, which is the part that failed here: the previous
harness stubbed audio_driver_mixer_add_stream and
audio_driver_mixer_stream_set_avail, so it modelled the two functions
that contained three of these four defects and could not have caught
any of them.  samples/audio/mixer_real links the real audio_mixer.c
against a genuine reserved mapping - everything past the resident
bound is PROT_NONE, so a read past it faults instead of passing - and
checks the >4 GiB length survives the load and that the bounded sniff
stays inside the head.

AAC PCM equivalence re-checked and still byte-exact over 144000
frames with the window starved to force backpressure.  audio_mixer.c
builds at C89 with RAAC+RMP4, with RWEBM+RVORBIS, with RAAC alone and
with none.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-08-14 05:19:17 +02:00
1 parent ef19c2f0a3
commit b0099a43e4
3 files changed
+75 -31

No files matched your search

+15 -10
View File
@@ -2478,7 +2478,7 @@ bool audio_driver_mixer_add_stream(audio_mixer_stream_params_t *params)
switch (params->type)
{
case AUDIO_MIXER_TYPE_WAV:
handle = audio_mixer_load_wav(buf, (int32_t)params->bufsize,
handle = audio_mixer_load_wav(buf, params->bufsize,
audio_driver_st.resampler_ident,
audio_driver_st.resampler_quality,
audio_driver_mixer_use_s16(
@@ -2499,37 +2499,38 @@ bool audio_driver_mixer_add_stream(audio_mixer_stream_params_t *params)
* them as it mixes, so the source stays owned by the sound
* (or by its lender) exactly as the compressed types' does */
handle = audio_mixer_load_wav_stream(buf,
(int32_t)params->bufsize);
params->bufsize);
break;
case AUDIO_MIXER_TYPE_OGG:
handle = audio_mixer_load_ogg(buf, (int32_t)params->bufsize);
handle = audio_mixer_load_ogg(buf, params->bufsize);
break;
case AUDIO_MIXER_TYPE_MOD:
handle = audio_mixer_load_mod(buf, (int32_t)params->bufsize);
handle = audio_mixer_load_mod(buf, params->bufsize);
break;
case AUDIO_MIXER_TYPE_FLAC:
#ifdef HAVE_RFLAC
handle = audio_mixer_load_flac(buf, (int32_t)params->bufsize);
handle = audio_mixer_load_flac(buf, params->bufsize);
#endif
break;
case AUDIO_MIXER_TYPE_MP3:
#ifdef HAVE_RMP3
handle = audio_mixer_load_mp3(buf, (int32_t)params->bufsize);
handle = audio_mixer_load_mp3(buf, params->bufsize);
#endif
break;
case AUDIO_MIXER_TYPE_M4A:
#ifdef HAVE_RAAC
handle = audio_mixer_load_m4a(buf, (int32_t)params->bufsize);
handle = audio_mixer_load_m4a(buf, params->bufsize);
#endif
break;
case AUDIO_MIXER_TYPE_OPUS:
#ifdef HAVE_ROPUS
handle = audio_mixer_load_opus(buf, (int32_t)params->bufsize);
handle = audio_mixer_load_opus(buf, params->bufsize);
#endif
break;
case AUDIO_MIXER_TYPE_WEBA:
#if defined(HAVE_RWEBM) && (defined(HAVE_ROPUS) || defined(HAVE_RVORBIS))
handle = audio_mixer_load_weba(buf, (int32_t)params->bufsize);
handle = audio_mixer_load_weba_avail(buf, params->bufsize,
params->avail);
#endif
break;
case AUDIO_MIXER_TYPE_NONE:
@@ -2554,9 +2555,13 @@ bool audio_driver_mixer_add_stream(audio_mixer_stream_params_t *params)
* skips its full-file end scan when it opens at play time. */
if (params->end_granule > 0)
audio_mixer_sound_set_end_granule(handle, params->end_granule);
#endif
/* NOT under HAVE_ROPUS: the resident bound applies to every
* windowed arm, and a build without Opus silently dropped it -
* the decoder then opened against the whole file's length with
* only the head committed. */
if (params->avail)
audio_mixer_sound_set_avail(handle, params->avail);
#endif
switch (params->state)
{
+46 -12
View File
@@ -138,7 +138,11 @@ struct audio_mixer_sound
{
/* shared streaming source (WAV / OGG / FLAC / MP3 / ...) */
const void* data;
unsigned size;
/* size_t, not unsigned: a windowed container is the whole
* file's length, and a 7 GB one truncated to 32 bits became
* 3.5 GB - the demuxer then treated every sample past that
* as out of range and the stream ended seconds in. */
size_t size;
} stream;
#endif
@@ -1043,7 +1047,7 @@ static bool wav_build_float(audio_mixer_sound_t* sound,
static bool wav_build_s16(audio_mixer_sound_t* sound,
enum resampler_quality quality);
audio_mixer_sound_t* audio_mixer_load_wav(void *buffer, int32_t size,
audio_mixer_sound_t* audio_mixer_load_wav(void *buffer, size_t size,
const char *resampler_ident, enum resampler_quality quality,
bool want_s16)
{
@@ -1114,7 +1118,7 @@ audio_mixer_sound_t* audio_mixer_load_wav(void *buffer, int32_t size,
#endif
}
audio_mixer_sound_t* audio_mixer_load_wav_stream(void *buffer, int32_t size)
audio_mixer_sound_t* audio_mixer_load_wav_stream(void *buffer, size_t size)
{
#ifdef HAVE_RWAV
audio_mixer_sound_t* sound;
@@ -1139,7 +1143,7 @@ audio_mixer_sound_t* audio_mixer_load_wav_stream(void *buffer, int32_t size)
#endif
}
audio_mixer_sound_t* audio_mixer_load_ogg(void *buffer, int32_t size)
audio_mixer_sound_t* audio_mixer_load_ogg(void *buffer, size_t size)
{
#if defined(HAVE_RVORBIS) || defined(HAVE_ROPUS) || defined(HAVE_RFLAC)
audio_mixer_sound_t* sound;
@@ -1182,7 +1186,7 @@ audio_mixer_sound_t* audio_mixer_load_ogg(void *buffer, int32_t size)
#endif
}
audio_mixer_sound_t* audio_mixer_load_flac(void *buffer, int32_t size)
audio_mixer_sound_t* audio_mixer_load_flac(void *buffer, size_t size)
{
#ifdef HAVE_RFLAC
audio_mixer_sound_t* sound = (audio_mixer_sound_t*)calloc(1, sizeof(*sound));
@@ -1200,7 +1204,7 @@ audio_mixer_sound_t* audio_mixer_load_flac(void *buffer, int32_t size)
#endif
}
audio_mixer_sound_t* audio_mixer_load_mp3(void *buffer, int32_t size)
audio_mixer_sound_t* audio_mixer_load_mp3(void *buffer, size_t size)
{
#ifdef HAVE_RMP3
audio_mixer_sound_t* sound = (audio_mixer_sound_t*)calloc(1, sizeof(*sound));
@@ -1218,7 +1222,7 @@ audio_mixer_sound_t* audio_mixer_load_mp3(void *buffer, int32_t size)
#endif
}
audio_mixer_sound_t* audio_mixer_load_m4a(void *buffer, int32_t size)
audio_mixer_sound_t* audio_mixer_load_m4a(void *buffer, size_t size)
{
#ifdef HAVE_RAAC
audio_mixer_sound_t* sound = (audio_mixer_sound_t*)calloc(1, sizeof(*sound));
@@ -1236,7 +1240,7 @@ audio_mixer_sound_t* audio_mixer_load_m4a(void *buffer, int32_t size)
#endif
}
audio_mixer_sound_t* audio_mixer_load_opus(void *buffer, int32_t size)
audio_mixer_sound_t* audio_mixer_load_opus(void *buffer, size_t size)
{
#ifdef HAVE_ROPUS
audio_mixer_sound_t* sound = (audio_mixer_sound_t*)calloc(1, sizeof(*sound));
@@ -1254,13 +1258,19 @@ audio_mixer_sound_t* audio_mixer_load_opus(void *buffer, int32_t size)
#endif
}
audio_mixer_sound_t* audio_mixer_load_weba(void *buffer, int32_t size)
audio_mixer_sound_t* audio_mixer_load_weba_avail(void *buffer, size_t size,
size_t avail)
{
#if defined(HAVE_RWEBM) && (defined(HAVE_ROPUS) || defined(HAVE_RVORBIS) \
|| defined(HAVE_RAAC) || defined(HAVE_RFLAC))
audio_mixer_sound_t* sound;
/* Bound the sniff to what is resident. This runs before the sound
* exists, so sound_set_avail cannot have been applied yet - and
* with the whole file's length it walked the segment straight off
* the committed head of a windowed buffer and died inside
* rwebm_open_memory_avail. */
enum audio_type_enum ty = audio_transfer_webm_audio_type(buffer,
(size_t)size);
(avail && avail < size) ? avail : size);
enum audio_mixer_type mt;
/* Resolve to the existing sound type whose streaming arm accepts
@@ -1287,11 +1297,17 @@ audio_mixer_sound_t* audio_mixer_load_weba(void *buffer, int32_t size)
sound->types.stream.data = buffer;
return sound;
#else
(void)avail;
return NULL;
#endif
}
audio_mixer_sound_t* audio_mixer_load_mod(void *buffer, int32_t size)
audio_mixer_sound_t* audio_mixer_load_weba(void *buffer, size_t size)
{
return audio_mixer_load_weba_avail(buffer, size, 0);
}
audio_mixer_sound_t* audio_mixer_load_mod(void *buffer, size_t size)
{
#ifdef HAVE_RMODTRACKER
audio_mixer_sound_t* sound = (audio_mixer_sound_t*)calloc(1, sizeof(*sound));
@@ -1349,7 +1365,8 @@ void audio_mixer_sound_set_end_granule(audio_mixer_sound_t *sound,
* 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))
#if (defined(HAVE_RWEBM) && (defined(HAVE_ROPUS) || defined(HAVE_RVORBIS))) \
|| defined(HAVE_RAAC) || defined(HAVE_RFLAC)
if (!voice)
return;
#ifdef AUDIO_MIXER_HAS_STREAM
@@ -1367,6 +1384,23 @@ void audio_mixer_voice_set_avail(audio_mixer_voice_t *voice, size_t avail)
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;
+14 -9
View File
@@ -76,21 +76,26 @@ void audio_mixer_done(void);
* mixer's current mode will play. The other format derives on
* demand at the first mode-mismatched play (see the derivation notes
* in the implementation), so a WAV holds one PCM copy, not two. */
audio_mixer_sound_t* audio_mixer_load_wav(void *buffer, int32_t size,
audio_mixer_sound_t* audio_mixer_load_wav(void *buffer, size_t size,
const char *resampler_ident, enum resampler_quality quality,
bool want_s16);
audio_mixer_sound_t* audio_mixer_load_wav_stream(void *buffer,
int32_t size);
audio_mixer_sound_t* audio_mixer_load_ogg(void *buffer, int32_t size);
audio_mixer_sound_t* audio_mixer_load_mod(void *buffer, int32_t size);
audio_mixer_sound_t* audio_mixer_load_flac(void *buffer, int32_t size);
audio_mixer_sound_t* audio_mixer_load_mp3(void *buffer, int32_t size);
audio_mixer_sound_t* audio_mixer_load_m4a(void *buffer, int32_t size);
audio_mixer_sound_t* audio_mixer_load_opus(void *buffer, int32_t size);
size_t size);
audio_mixer_sound_t* audio_mixer_load_ogg(void *buffer, size_t size);
audio_mixer_sound_t* audio_mixer_load_mod(void *buffer, size_t size);
audio_mixer_sound_t* audio_mixer_load_flac(void *buffer, size_t size);
audio_mixer_sound_t* audio_mixer_load_mp3(void *buffer, size_t size);
audio_mixer_sound_t* audio_mixer_load_m4a(void *buffer, size_t size);
audio_mixer_sound_t* audio_mixer_load_opus(void *buffer, size_t size);
/* WebM audio (.weba): identifies the track's codec and returns a sound
* of the matching existing type (OPUS or OGG), or NULL when the
* container carries no supported track. */
audio_mixer_sound_t* audio_mixer_load_weba(void *buffer, int32_t size);
audio_mixer_sound_t* audio_mixer_load_weba(void *buffer, size_t size);
/* As above, but the codec sniff reads no further than 'avail' bytes.
* A windowed caller must use this: the plain form walks the segment
* with the file's whole length, which is not resident. */
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. */