From 03306cf667646dd207eee28e773e3ec9543b283f Mon Sep 17 00:00:00 2001
From: "Daniel K. O. (dkosmari)" <[EMAIL REDACTED]>
Date: Mon, 19 Jan 2026 21:26:13 -0300
Subject: [PATCH] - Remap channels from vorbis and opus sources to match SDL
channels. - Don't discard decoded samples when stream configuration changes.
---
CMakeLists.txt | 1 +
src/codecs/music_ogg.c | 30 ++++--
src/codecs/music_opus.c | 30 ++++--
src/codecs/remap_channels.c | 210 ++++++++++++++++++++++++++++++++++++
src/codecs/remap_channels.h | 29 +++++
5 files changed, 281 insertions(+), 19 deletions(-)
create mode 100644 src/codecs/remap_channels.c
create mode 100644 src/codecs/remap_channels.h
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e56c18188..1a962e874 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -233,6 +233,7 @@ add_library(SDL2_mixer
src/codecs/music_wav.c
src/codecs/music_wavpack.c
src/codecs/music_xmp.c
+ src/codecs/remap_channels.c
src/effect_position.c
src/effect_stereoreverse.c
src/effects_internal.c
diff --git a/src/codecs/music_ogg.c b/src/codecs/music_ogg.c
index a5ebd9569..0338ac8cb 100644
--- a/src/codecs/music_ogg.c
+++ b/src/codecs/music_ogg.c
@@ -26,6 +26,7 @@
#include "SDL_loadso.h"
#include "music_ogg.h"
+#include "remap_channels.h"
#include "utils.h"
#define OV_EXCLUDE_STATIC_CALLBACKS
@@ -195,6 +196,7 @@ static void OGG_Delete(void *context);
static int OGG_UpdateSection(OGG_music *music)
{
vorbis_info *vi;
+ int new_buffer_size;
vi = vorbis.ov_info(&music->vf, -1);
if (!vi) {
@@ -206,11 +208,6 @@ static int OGG_UpdateSection(OGG_music *music)
}
SDL_memcpy(&music->vi, vi, sizeof(*vi));
- if (music->buffer) {
- SDL_free(music->buffer);
- music->buffer = NULL;
- }
-
if (music->stream) {
SDL_FreeAudioStream(music->stream);
music->stream = NULL;
@@ -219,13 +216,24 @@ static int OGG_UpdateSection(OGG_music *music)
music->stream = SDL_NewAudioStream(AUDIO_S16SYS, (Uint8)vi->channels, (int)vi->rate,
music_spec.format, music_spec.channels, music_spec.freq);
if (!music->stream) {
+ SDL_free(music->buffer);
+ music->buffer = NULL;
+ music->buffer_size = 0;
return -1;
}
- music->buffer_size = music_spec.samples * (int)sizeof(Sint16) * vi->channels;
- music->buffer = (char *)SDL_malloc((size_t)music->buffer_size);
- if (!music->buffer) {
- return -1;
+ /* Note: never shrink the buffer, we just decoded data in there. */
+ new_buffer_size = music_spec.samples * (int)sizeof(Sint16) * vi->channels;
+ if (new_buffer_size > music->buffer_size) {
+ char *new_buffer = (char *)SDL_realloc(music->buffer, new_buffer_size);
+ if (!new_buffer) {
+ SDL_free(music->buffer);
+ music->buffer = NULL;
+ music->buffer_size = 0;
+ return -1;
+ }
+ music->buffer = new_buffer;
+ music->buffer_size = new_buffer_size;
}
return 0;
}
@@ -387,7 +395,7 @@ static int OGG_GetSome(void *context, void *data, int bytes, SDL_bool *done)
#ifdef OGG_USE_TREMOR
amount = (int)vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, §ion);
#else
- amount = (int)vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, SDL_BYTEORDER == SDL_BIG_ENDIAN, 2, 1, §ion);
+ amount = (int)vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, SDL_BYTEORDER == SDL_BIG_ENDIAN, (int)sizeof(Sint16), 1, §ion);
#endif
if (amount < 0) {
return set_ov_error("ov_read", amount);
@@ -400,6 +408,8 @@ static int OGG_GetSome(void *context, void *data, int bytes, SDL_bool *done)
}
}
+ remap_channels_vorbis((Sint16 *)music->buffer, amount / (int)sizeof(Sint16), music->vi.channels);
+
pcmPos = vorbis.ov_pcm_tell(&music->vf);
if (music->loop && (music->play_count != 1) && (pcmPos >= music->loop_end)) {
amount -= (int)((pcmPos - music->loop_end) * music->vi.channels) * (int)sizeof(Sint16);
diff --git a/src/codecs/music_opus.c b/src/codecs/music_opus.c
index a367fbaa3..648d50cb6 100644
--- a/src/codecs/music_opus.c
+++ b/src/codecs/music_opus.c
@@ -26,6 +26,7 @@
#include "SDL_loadso.h"
#include "music_opus.h"
+#include "remap_channels.h"
#include "utils.h"
#ifdef OPUSFILE_HEADER
@@ -169,6 +170,7 @@ static void OPUS_Delete(void*);
static int OPUS_UpdateSection(OPUS_music *music)
{
const OpusHead *op_info;
+ int new_buffer_size;
op_info = opus.op_head(music->of, -1);
if (!op_info) {
@@ -180,11 +182,6 @@ static int OPUS_UpdateSection(OPUS_music *music)
}
music->op_info = op_info;
- if (music->buffer) {
- SDL_free(music->buffer);
- music->buffer = NULL;
- }
-
if (music->stream) {
SDL_FreeAudioStream(music->stream);
music->stream = NULL;
@@ -193,13 +190,24 @@ static int OPUS_UpdateSection(OPUS_music *music)
music->stream = SDL_NewAudioStream(AUDIO_S16SYS, (Uint8)op_info->channel_count, 48000,
music_spec.format, music_spec.channels, music_spec.freq);
if (!music->stream) {
+ SDL_free(music->buffer);
+ music->buffer = NULL;
+ music->buffer_size = 0;
return -1;
}
- music->buffer_size = (int)music_spec.samples * (int)sizeof(opus_int16) * op_info->channel_count;
- music->buffer = (char *)SDL_malloc((size_t)music->buffer_size);
- if (!music->buffer) {
- return -1;
+ /* Note: never shrink the buffer, we just decoded data in there. */
+ new_buffer_size = (int)music_spec.samples * (int)sizeof(opus_int16) * op_info->channel_count;
+ if (new_buffer_size > music->buffer_size) {
+ char *new_buffer = (char *)SDL_realloc(music->buffer, (size_t)new_buffer_size);
+ if (!new_buffer) {
+ SDL_free(music->buffer);
+ music->buffer = NULL;
+ music->buffer_size = 0;
+ return -1;
+ }
+ music->buffer = new_buffer;
+ music->buffer_size = new_buffer_size;
}
return 0;
}
@@ -378,6 +386,10 @@ static int OPUS_GetSome(void *context, void *data, int bytes, SDL_bool *done)
}
}
+ if (music->op_info->mapping_family == 1) {
+ remap_channels_vorbis((Sint16 *)music->buffer, samples * music->op_info->channel_count, music->op_info->channel_count);
+ }
+
pcmPos = opus.op_pcm_tell(music->of);
if (music->loop && (music->play_count != 1) && (pcmPos >= music->loop_end)) {
samples -= (int)((pcmPos - music->loop_end) * music->op_info->channel_count) * (int)sizeof(Sint16);
diff --git a/src/codecs/remap_channels.c b/src/codecs/remap_channels.c
new file mode 100644
index 000000000..552b06c1d
--- /dev/null
+++ b/src/codecs/remap_channels.c
@@ -0,0 +1,210 @@
+/*
+ SDL_mixer: An audio mixer library based on the SDL library
+ Copyright (C) 2026 Daniel K. O. <github.com/dkosmari>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+/*
+ * Comparison of channel orders:
+ *
+ * | Num. | chan. | SDL | FLAC | MS/USB | Vorbis |
+ * |------:|------:|-----|------|--------|--------|
+ * | 2 | 1 | FL | FL | FL | FL |
+ * | | 2 | FR | FR | FR | FR |
+ * |-------|-------|-----|------|--------|--------|
+ * | 3 | 1 | FL | FL | FL | FL |
+ * | | 2 | FR | FR | FR | FC |
+ * | | 3 | LFE | FC | FC/LFE | FR |
+ * |-------|-------|-----|------|--------|--------|
+ * | 4 | 1 | FL | FL | FL | FL |
+ * | | 2 | FR | FR | FR | FR |
+ * | | 3 | RL | RL | RL | RL |
+ * | | 4 | RR | RR | RR | RR |
+ * |-------|-------|-----|------|--------|--------|
+ * | 5 | 1 | FL | FL | FL | FL |
+ * | (5.0) | 2 | FR | FR | FR | FC |
+ * | | 3 | LFE | FC | FC/LFE | FR |
+ * | | 4 | RL | RL | RL | RL |
+ * | | 5 | RR | RR | RR | RR |
+ * |-------|-------|-----|------|--------|--------|
+ * | 6 | 1 | FL | FL | FL | FL |
+ * | (5.1) | 2 | FR | FR | FR | FC |
+ * | | 3 | FC | FC | FC | FR |
+ * | | 4 | LFE | LFE | LFE | RL |
+ * | | 5 | RL | RL | RR | RR |
+ * | | 6 | RR | RR | RR | LFE |
+ * |-------|-------|-----|------|--------|--------|
+ * | 7 | 1 | FL | FL | FL | FL |
+ * | | 2 | FR | FR | FR | FC |
+ * | | 3 | FC | FC | FC | FR |
+ * | | 4 | LFE | LFE | LFE | SL |
+ * | | 5 | RC | RC | RC | SR |
+ * | | 6 | SL | SL | SL | RC |
+ * | | 7 | SR | SR | SR | LFE |
+ * |-------|-------|-----|------|--------|--------|
+ * | 8 | 1 | FL | FL | FL | FL |
+ * | (7.1) | 2 | FR | FR | FR | FC |
+ * | | 3 | FC | FC | FC | FR |
+ * | | 4 | LFE | LFE | LFE | SL |
+ * | | 5 | RL | RL | RL | SR |
+ * | | 6 | RR | RR | RR | RL |
+ * | | 7 | SL | SL | SL | RR |
+ * | | 8 | SR | SR | SR | LFE |
+ *
+ *
+ * **Note:** USB/MS use a bitmask to indicate which channels are present. The only
+ * requirement is that they must appear in a fixed order, if present:
+ *
+ * - FL (Front Center)
+ * - FR (Front Right)
+ * - FC (Front Center)
+ * - LFE (Low Frequency Enhancement)
+ * - BL (Back Left) aka RL (Rear LEft)
+ * - BR (Back Right) aka RR (Rear Right)
+ * - FLC (Front Left of Center)
+ * - FRC (Front Right of Center)
+ * - BC (Back Center) aka RC (Rear Center)
+ * - SL (Side Left)
+ * - SR (Side Right)
+ * - TC (Top Center)
+ * - TFL (Top Front Left)
+ * - TFC (Top Front Center)
+ * - TFR (Top Front Right)
+ * - TBL (Top Back Left)
+ * - TBC (Top Back Center)
+ * - TBR (Top Back Right)
+ *
+ *
+ * **Note:** WavPack documentation claims that ALL MS/USB channels (up to the max) must be
+ * present. So to contain all the 7.1 channels, a .wv file must have 11 channels, with
+ * silent FLC, FRC, BC/RC channels.
+ *
+ *
+ * Sources:
+ * - Vorbis: https://www.rfc-editor.org/rfc/rfc7845.html#section-5.1.1.2
+ * - SDL: https://github.com/libsdl-org/SDL/blob/main/include/SDL3/SDL_audio.h
+ * - FLAC: https://www.rfc-editor.org/rfc/rfc9639.html#name-channels-bits
+ * - WavPack (WV): https://www.wavpack.com/wavpack_doc.html
+ * - USB: https://www.usb.org/sites/default/files/audio10.pdf (see "3.7.2.3 Audio Channel Cluster Format")
+ * - MS: https://learn.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible
+ */
+
+#include "remap_channels.h"
+
+
+static void remap_channels_vorbis_3(Sint16 *samples, int num_samples)
+{
+ /* Note: this isn't perfect, because we map FC to LFE */
+ int i;
+ for (i = 0; i < num_samples; i += 3) {
+ Sint16 FC = samples[i + 1];
+ Sint16 FR = samples[i + 2];
+ samples[i + 1] = FR;
+ samples[i + 2] = FC;
+ }
+}
+
+static void remap_channels_vorbis_5(Sint16 *samples, int num_samples)
+{
+ /* Note: this isn't perfect, because we map FC to LFE. */
+ int i;
+ for (i = 0; i < num_samples; i += 5) {
+ Sint16 FC = samples[i + 1];
+ Sint16 FR = samples[i + 2];
+ samples[i + 1] = FR;
+ samples[i + 2] = FC;
+ }
+}
+
+static void remap_channels_vorbis_5_1(Sint16 *samples, int num_samples)
+{
+ int i;
+ for (i = 0; i < num_samples; i += 6) {
+ Sint16 FC = samples[i + 1];
+ Sint16 FR = samples[i + 2];
+ Sint16 RL = samples[i + 3];
+ Sint16 RR = samples[i + 4];
+ Sint16 LFE = samples[i + 5];
+ samples[i + 1] = FR;
+ samples[i + 2] = FC;
+ samples[i + 3] = LFE;
+ samples[i + 4] = RL;
+ samples[i + 5] = RR;
+ }
+}
+
+static void remap_channels_vorbis_7(Sint16 *samples, int num_samples)
+{
+ int i = 0;
+ for (i = 0; i < num_samples; i += 7) {
+ Sint16 FC = samples[i + 1];
+ Sint16 FR = samples[i + 2];
+ Sint16 SL = samples[i + 3];
+ Sint16 SR = samples[i + 4];
+ Sint16 RC = samples[i + 5];
+ Sint16 LFE = samples[i + 6];
+ samples[i + 1] = FR;
+ samples[i + 2] = FC;
+ samples[i + 3] = LFE;
+ samples[i + 4] = RC;
+ samples[i + 5] = SL;
+ samples[i + 6] = SR;
+ }
+}
+
+static void remap_channels_vorbis_7_1(Sint16 *samples, int num_samples)
+{
+ int i = 0;
+ for (i = 0; i < num_samples; i += 8) {
+ Sint16 FC = samples[i + 1];
+ Sint16 FR = samples[i + 2];
+ Sint16 SL = samples[i + 3];
+ Sint16 SR = samples[i + 4];
+ Sint16 RL = samples[i + 5];
+ Sint16 RR = samples[i + 6];
+ Sint16 LFE = samples[i + 7];
+ samples[i + 1] = FR;
+ samples[i + 2] = FC;
+ samples[i + 3] = LFE;
+ samples[i + 4] = RL;
+ samples[i + 5] = RR;
+ samples[i + 6] = SL;
+ samples[i + 7] = SR;
+ }
+}
+
+void remap_channels_vorbis(Sint16 *samples, int num_samples, int num_channels)
+{
+ switch (num_channels) {
+ case 3:
+ remap_channels_vorbis_3(samples, num_samples);
+ break;
+ case 5:
+ remap_channels_vorbis_5(samples, num_samples);
+ break;
+ case 6:
+ remap_channels_vorbis_5_1(samples, num_samples);
+ break;
+ case 7:
+ remap_channels_vorbis_7(samples, num_samples);
+ break;
+ case 8:
+ remap_channels_vorbis_7_1(samples, num_samples);
+ break;
+ }
+}
diff --git a/src/codecs/remap_channels.h b/src/codecs/remap_channels.h
new file mode 100644
index 000000000..235364871
--- /dev/null
+++ b/src/codecs/remap_channels.h
@@ -0,0 +1,29 @@
+/*
+ SDL_mixer: An audio mixer library based on the SDL library
+ Copyright (C) 2026 Daniel K. O. <github.com/dkosmari>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifndef REMAP_CHANNELS_H_
+#define REMAP_CHANNELS_H_
+
+#include "SDL_types.h"
+
+extern void remap_channels_vorbis(Sint16 *samples, int num_samples, int num_channels);
+
+#endif /* CHANNEL_REMAP_H_ */