浏览代码

audio: Separate channel maps out of SDL_AudioSpec.

Ryan C. Gordon 9 月之前
父节点
当前提交
4755055bc3

+ 177 - 18
include/SDL3/SDL_audio.h

@@ -43,7 +43,12 @@
  * if you aren't reading from a file) as a basic means to load sound data into
  * your program.
  *
- * ## Channel layouts as SDL expects them
+ * ## Channel layouts
+ *
+ * Audio data passing through SDL is uncompressed PCM data, interleaved.
+ * One can provide their own decompression through an MP3, etc, decoder, but
+ * SDL does not provide this directly. Each interleaved channel of data is
+ * meant to be in a specific order.
  *
  * Abbreviations:
  *
@@ -76,7 +81,7 @@
  * platforms; SDL will swizzle the channels as necessary if a platform expects
  * something different.
  *
- * SDL_AudioStream can also be provided a channel map to change this ordering
+ * SDL_AudioStream can also be provided channel maps to change this ordering
  * to whatever is necessary, in other audio processing scenarios.
  */
 
@@ -301,18 +306,6 @@ typedef Uint32 SDL_AudioDeviceID;
  */
 #define SDL_AUDIO_DEVICE_DEFAULT_RECORDING ((SDL_AudioDeviceID) 0xFFFFFFFE)
 
-/**
- * Maximum channels that an SDL_AudioSpec channel map can handle.
- *
- * This is (currently) double the number of channels that SDL supports, to
- * allow for future expansion while maintaining binary compatibility.
- *
- * \since This macro is available since SDL 3.0.0.
- *
- * \sa SDL_AudioSpec
- */
-#define SDL_MAX_CHANNEL_MAP_SIZE 16
-
 /**
  * Format specifier for audio data.
  *
@@ -325,8 +318,6 @@ typedef struct SDL_AudioSpec
     SDL_AudioFormat format;     /**< Audio data format */
     int channels;               /**< Number of channels: 1 mono, 2 stereo, etc */
     int freq;                   /**< sample rate: sample frames per second */
-    SDL_bool use_channel_map;   /**< If SDL_FALSE, ignore `channel_map` and use default order. */
-    Uint8 channel_map[SDL_MAX_CHANNEL_MAP_SIZE];      /**< `channels` items of channel order. */
 } SDL_AudioSpec;
 
 /**
@@ -560,6 +551,29 @@ extern SDL_DECLSPEC const char *SDLCALL SDL_GetAudioDeviceName(SDL_AudioDeviceID
  */
 extern SDL_DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *sample_frames);
 
+/**
+ * Get the current channel map of an audio device.
+ *
+ * Channel maps are optional; most things do not need them, instead passing
+ * data in the [order that SDL expects](CategoryAudio#channel-layouts).
+ *
+ * Audio devices usually have no remapping applied. This is represented by
+ * returning NULL, and does not signify an error.
+ *
+ * The returned array follows the SDL_GetStringRule (even though, strictly
+ * speaking, it isn't a string, it has the same memory manangement rules).
+ *
+ * \param devid the instance ID of the device to query.
+ * \param count On output, set to number of channels in the map. Can be NULL.
+ * \returns an array of the current channel mapping, with as many elements as the current output spec's channels, or NULL if default.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamInputChannelMap
+ */
+extern SDL_DECLSPEC const int * SDLCALL SDL_GetAudioDeviceChannelMap(SDL_AudioDeviceID devid, int *count);
 
 /**
  * Open a specific audio device.
@@ -1081,6 +1095,151 @@ extern SDL_DECLSPEC float SDLCALL SDL_GetAudioStreamGain(SDL_AudioStream *stream
  */
 extern SDL_DECLSPEC int SDLCALL SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain);
 
+/**
+ * Get the current input channel map of an audio stream.
+ *
+ * Channel maps are optional; most things do not need them, instead passing
+ * data in the [order that SDL expects](CategoryAudio#channel-layouts).
+ *
+ * Audio streams default to no remapping applied. This is represented by
+ * returning NULL, and does not signify an error.
+ *
+ * The returned array follows the SDL_GetStringRule (even though, strictly
+ * speaking, it isn't a string, it has the same memory manangement rules).
+ *
+ * \param stream the SDL_AudioStream to query.
+ * \param count On output, set to number of channels in the map. Can be NULL.
+ * \returns an array of the current channel mapping, with as many elements as the current output spec's channels, or NULL if default.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ *               a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamInputChannelMap
+ */
+extern SDL_DECLSPEC const int * SDLCALL SDL_GetAudioStreamInputChannelMap(SDL_AudioStream *stream, int *count);
+
+/**
+ * Get the current output channel map of an audio stream.
+ *
+ * Channel maps are optional; most things do not need them, instead passing
+ * data in the [order that SDL expects](CategoryAudio#channel-layouts).
+ *
+ * Audio streams default to no remapping applied. This is represented by
+ * returning NULL, and does not signify an error.
+ *
+ * The returned array follows the SDL_GetStringRule (even though, strictly
+ * speaking, it isn't a string, it has the same memory manangement rules).
+ *
+ * \param stream the SDL_AudioStream to query.
+ * \param count On output, set to number of channels in the map. Can be NULL.
+ * \returns an array of the current channel mapping, with as many elements as the current output spec's channels, or NULL if default.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ *               a stream-specific mutex while running.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamInputChannelMap
+ */
+extern SDL_DECLSPEC const int * SDLCALL SDL_GetAudioStreamOutputChannelMap(SDL_AudioStream *stream, int *count);
+
+/**
+ * Set the current input channel map of an audio stream.
+ *
+ * Channel maps are optional; most things do not need them, instead passing
+ * data in the [order that SDL expects](CategoryAudio#channel-layouts).
+ *
+ * The input channel map reorders data that is added to a stream via
+ * SDL_PutAudioStreamData. Future calls to SDL_PutAudioStreamData
+ * must provide data in the new channel order.
+ *
+ * Each item in the array represents an input channel, and its value is the
+ * channel that it should be remapped to. To reverse a stereo signal's left
+ * and right values, you'd have an array of `{ 1, 0 }`. It is legal to remap
+ * multiple channels to the same thing, so `{ 1, 1 }` would duplicate the
+ * right channel to both channels of a stereo signal. You cannot change the
+ * number of channels through a channel map, just reorder them.
+ *
+ * Data that was previously queued in the stream will still be operated on in
+ * the order that was current when it was added, which is to say you can put
+ * the end of a sound file in one order to a stream, change orders for the
+ * next sound file, and start putting that new data while the previous sound
+ * file is still queued, and everything will still play back correctly.
+ *
+ * Audio streams default to no remapping applied. Passing a NULL channel map
+ * is legal, and turns off remapping.
+ *
+ * SDL will copy the channel map; the caller does not have to save this array
+ * after this call.
+ *
+ * If `count` is not equal to the current number of channels in the audio
+ * stream's format, this will fail. This is a safety measure to make sure a
+ * a race condition hasn't changed the format while you this call is setting
+ * the channel map.
+ *
+ * \param stream the SDL_AudioStream to change.
+ * \param chmap the new channel map, NULL to reset to default.
+ * \param count The number of channels in the map.
+ * \returns 0 on success, -1 on error.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ *               a stream-specific mutex while running. Don't change the
+ *               stream's format to have a different number of channels from a
+ *               a different thread at the same time, though!
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamInputChannelMap
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAudioStreamInputChannelMap(SDL_AudioStream *stream, const int *chmap, int count);
+
+/**
+ * Set the current output channel map of an audio stream.
+ *
+ * Channel maps are optional; most things do not need them, instead passing
+ * data in the [order that SDL expects](CategoryAudio#channel-layouts).
+ *
+ * The output channel map reorders data that leaving a stream via
+ * SDL_GetAudioStreamData.
+ *
+ * Each item in the array represents an output channel, and its value is the
+ * channel that it should be remapped to. To reverse a stereo signal's left
+ * and right values, you'd have an array of `{ 1, 0 }`. It is legal to remap
+ * multiple channels to the same thing, so `{ 1, 1 }` would duplicate the
+ * right channel to both channels of a stereo signal. You cannot change the
+ * number of channels through a channel map, just reorder them.
+ *
+ * The output channel map can be changed at any time, as output remapping is
+ * applied during SDL_GetAudioStreamData.
+ *
+ * Audio streams default to no remapping applied. Passing a NULL channel map
+ * is legal, and turns off remapping.
+ *
+ * SDL will copy the channel map; the caller does not have to save this array
+ * after this call.
+ *
+ * If `count` is not equal to the current number of channels in the audio
+ * stream's format, this will fail. This is a safety measure to make sure a
+ * a race condition hasn't changed the format while you this call is setting
+ * the channel map.
+ *
+ * \param stream the SDL_AudioStream to change.
+ * \param chmap the new channel map, NULL to reset to default.
+ * \param count The number of channels in the map.
+ * \returns 0 on success, -1 on error.
+ *
+ * \threadsafety It is safe to call this function from any thread, as it holds
+ *               a stream-specific mutex while running. Don't change the
+ *               stream's format to have a different number of channels from a
+ *               a different thread at the same time, though!
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetAudioStreamInputChannelMap
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetAudioStreamOutputChannelMap(SDL_AudioStream *stream, const int *chmap, int count);
 
 /**
  * Add data to the stream.
@@ -1505,7 +1664,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream)
  * Also unlike other functions, the audio device begins paused. This is to map
  * more closely to SDL2-style behavior, since there is no extra step here to
  * bind a stream to begin audio flowing. The audio device should be resumed
- * with `SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));`
+ * with `SDL_ResumeAudioStreamDevice(stream);`
  *
  * This function works with both playback and recording devices.
  *
@@ -1547,7 +1706,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream)
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_GetAudioStreamDevice
- * \sa SDL_ResumeAudioDevice
+ * \sa SDL_ResumeAudioStreamDevice
  */
 extern SDL_DECLSPEC SDL_AudioStream *SDLCALL SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec, SDL_AudioStreamCallback callback, void *userdata);
 

+ 41 - 9
src/audio/SDL_audio.c

@@ -167,6 +167,16 @@ static int GetDefaultSampleFramesFromFreq(const int freq)
     }
 }
 
+int *SDL_ChannelMapDup(const int *origchmap, int channels)
+{
+    const size_t chmaplen = sizeof (*origchmap) * channels;
+    int *chmap = (int *) SDL_malloc(chmaplen);
+    if (chmap) {
+        SDL_memcpy(chmap, origchmap, chmaplen);
+    }
+    return chmap;
+}
+
 void OnAudioStreamCreated(SDL_AudioStream *stream)
 {
     SDL_assert(stream != NULL);
@@ -243,17 +253,18 @@ static void UpdateAudioStreamFormatsPhysical(SDL_AudioDevice *device)
                 // SDL_SetAudioStreamFormat does a ton of validation just to memcpy an audiospec.
                 SDL_LockMutex(stream->lock);
                 SDL_copyp(&stream->dst_spec, &spec);
+                SDL_SetAudioStreamOutputChannelMap(stream, device->chmap, spec.channels);  // this should be fast for normal cases, though!
                 SDL_UnlockMutex(stream->lock);
             }
         }
     }
 }
 
-SDL_bool SDL_AudioSpecsEqual(const SDL_AudioSpec *a, const SDL_AudioSpec *b)
+SDL_bool SDL_AudioSpecsEqual(const SDL_AudioSpec *a, const SDL_AudioSpec *b, const int *channel_map_a, const int *channel_map_b)
 {
-    if ((a->format != b->format) || (a->channels != b->channels) || (a->freq != b->freq) || (a->use_channel_map != b->use_channel_map)) {
+    if ((a->format != b->format) || (a->channels != b->channels) || (a->freq != b->freq) || ((channel_map_a != NULL) != (channel_map_b != NULL))) {
         return SDL_FALSE;
-    } else if (a->use_channel_map && (SDL_memcmp(a->channel_map, b->channel_map, sizeof (a->channel_map[0]) * a->channels) != 0)) {
+    } else if (channel_map_a && (SDL_memcmp(channel_map_a, channel_map_b, sizeof (*channel_map_a) * a->channels) != 0)) {
         return SDL_FALSE;
     }
     return SDL_TRUE;
@@ -533,6 +544,7 @@ static void DestroyPhysicalAudioDevice(SDL_AudioDevice *device)
     SDL_DestroyMutex(device->lock);
     SDL_DestroyCondition(device->close_cond);
     SDL_free(device->work_buffer);
+    SDL_FreeLater(device->chmap);  // this pointer is handed to the app during SDL_GetAudioDeviceChannelMap
     SDL_FreeLater(device->name);  // this pointer is handed to the app during SDL_GetAudioDeviceName
     SDL_free(device);
 }
@@ -648,7 +660,6 @@ SDL_AudioDevice *SDL_AddAudioDevice(SDL_bool recording, const char *name, const
         spec.channels = default_channels;
         spec.freq = default_freq;
     } else {
-        SDL_assert(!inspec->use_channel_map);  // backends shouldn't set a channel map here! Set it when opening the device!
         spec.format = (inspec->format != 0) ? inspec->format : default_format;
         spec.channels = (inspec->channels != 0) ? inspec->channels : default_channels;
         spec.freq = (inspec->freq != 0) ? inspec->freq : default_freq;
@@ -1101,7 +1112,7 @@ SDL_bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device)
             SDL_AudioStream *stream = logdev->bound_streams;
 
             // We should have updated this elsewhere if the format changed!
-            SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &device->spec));
+            SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &device->spec, stream->dst_chmap, device->chmap));
 
             const int br = SDL_AtomicGet(&logdev->paused) ? 0 : SDL_GetAudioStreamDataAdjustGain(stream, device_buffer, buffer_size, logdev->gain);
             if (br < 0) {  // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow.
@@ -1137,7 +1148,7 @@ SDL_bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device)
 
                 for (SDL_AudioStream *stream = logdev->bound_streams; stream; stream = stream->next_binding) {
                     // We should have updated this elsewhere if the format changed!
-                    SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &outspec));
+                    SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &outspec, stream->dst_chmap, device->chmap));
 
                     /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams
                        for iterating here because the binding linked list can only change while the device lock is held.
@@ -1448,6 +1459,25 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *
     return retval;
 }
 
+const int *SDL_GetAudioDeviceChannelMap(SDL_AudioDeviceID devid, int *count)
+{
+    const int *retval = NULL;
+    int channels = 0;
+    SDL_AudioDevice *device = ObtainPhysicalAudioDeviceDefaultAllowed(devid);
+    if (device) {
+        retval = device->chmap;
+        channels = device->spec.channels;
+    }
+    ReleaseAudioDevice(device);
+
+    if (count) {
+        *count = channels;
+    }
+
+    return retval;
+}
+
+
 // this is awkward, but this makes sure we can release the device lock
 //  so the device thread can terminate but also not have two things
 //  race to close or open the device while the lock is unprotected.
@@ -1618,7 +1648,6 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec
     device->spec.format = (SDL_AUDIO_BITSIZE(device->default_spec.format) >= SDL_AUDIO_BITSIZE(spec.format)) ? device->default_spec.format : spec.format;
     device->spec.freq = SDL_max(device->default_spec.freq, spec.freq);
     device->spec.channels = SDL_max(device->default_spec.channels, spec.channels);
-    device->spec.use_channel_map = SDL_FALSE;  // all initial channel map requests are denied, since we might have to change channel counts.
     device->sample_frames = GetDefaultSampleFramesFromFreq(device->spec.freq);
     SDL_UpdatedAudioDeviceFormat(device);  // start this off sane.
 
@@ -1906,6 +1935,7 @@ int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int
                     if (logdev->postmix) {
                         stream->src_spec.format = SDL_AUDIO_F32;
                     }
+                    SDL_SetAudioStreamInputChannelMap(stream, device->chmap, device->spec.channels);  // this should be fast for normal cases, though!
                 }
 
                 SDL_UnlockMutex(stream->lock);
@@ -2208,7 +2238,8 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
         }
 
         if (needs_migration) {
-            const SDL_bool spec_changed = !SDL_AudioSpecsEqual(&current_default_device->spec, &new_default_device->spec);
+            // we don't currently report channel map changes, so we'll leave them as NULL for now.
+            const SDL_bool spec_changed = !SDL_AudioSpecsEqual(&current_default_device->spec, &new_default_device->spec, NULL, NULL);
             SDL_LogicalAudioDevice *next = NULL;
             for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev; logdev = next) {
                 next = logdev->next;
@@ -2288,7 +2319,8 @@ int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL
 {
     const int orig_work_buffer_size = device->work_buffer_size;
 
-    if (SDL_AudioSpecsEqual(&device->spec, newspec) && (new_sample_frames == device->sample_frames)) {
+    // we don't currently have any place where channel maps change from under you, but we can check that if necessary later.
+    if (SDL_AudioSpecsEqual(&device->spec, newspec, NULL, NULL) && (new_sample_frames == device->sample_frames)) {
         return 0;  // we're already in that format.
     }
 

+ 145 - 43
src/audio/SDL_audiocvt.c

@@ -124,11 +124,12 @@ static SDL_bool SDL_IsSupportedChannelCount(const int channels)
     return ((channels >= 1) && (channels <= 8));
 }
 
-SDL_bool SDL_ChannelMapIsBogus(const Uint8 *map, int channels)
+SDL_bool SDL_ChannelMapIsBogus(const int *chmap, int channels)
 {
-    if (map) {
+    if (chmap) {
         for (int i = 0; i < channels; i++) {
-            if (map[i] >= ((Uint8) channels)) {
+            const int mapping = chmap[i];
+            if ((mapping < 0) || (mapping >= channels)) {
                 return SDL_TRUE;
             }
         }
@@ -136,11 +137,11 @@ SDL_bool SDL_ChannelMapIsBogus(const Uint8 *map, int channels)
     return SDL_FALSE;
 }
 
-SDL_bool SDL_ChannelMapIsDefault(const Uint8 *map, int channels)
+SDL_bool SDL_ChannelMapIsDefault(const int *chmap, int channels)
 {
-    if (map) {
+    if (chmap) {
         for (int i = 0; i < channels; i++) {
-            if (map[i] != i) {
+            if (chmap[i] != i) {
                 return SDL_FALSE;
             }
         }
@@ -149,7 +150,7 @@ SDL_bool SDL_ChannelMapIsDefault(const Uint8 *map, int channels)
 }
 
 // Swizzle audio channels. src and dst can be the same pointer. It does not change the buffer size.
-static void SwizzleAudio(const int num_frames, void *dst, const void *src, int channels, const Uint8 *map, int bitsize)
+static void SwizzleAudio(const int num_frames, void *dst, const void *src, int channels, const int *map, int bitsize)
 {
     #define CHANNEL_SWIZZLE(bits) { \
         Uint##bits *tdst = (Uint##bits *) dst; /* treat as UintX; we only care about moving bits and not the type here. */ \
@@ -161,16 +162,18 @@ static void SwizzleAudio(const int num_frames, void *dst, const void *src, int c
                 } \
             } \
         } else { \
-            Uint##bits tmp[SDL_MAX_CHANNEL_MAP_SIZE]; \
-            SDL_zeroa(tmp); \
-            SDL_assert(SDL_arraysize(tmp) >= channels); \
-            for (int i = 0; i < num_frames; i++, tsrc += channels, tdst += channels) { \
-                for (int ch = 0; ch < channels; ch++) { \
-                    tmp[ch] = tsrc[map[ch]]; \
-                } \
-                for (int ch = 0; ch < channels; ch++) { \
-                    tdst[ch] = tmp[ch]; \
+            SDL_bool isstack; \
+            Uint##bits *tmp = (Uint##bits *) SDL_small_alloc(int, channels, &isstack); /* !!! FIXME: allocate this when setting the channel map instead. */ \
+            if (tmp) { \
+                for (int i = 0; i < num_frames; i++, tsrc += channels, tdst += channels) { \
+                    for (int ch = 0; ch < channels; ch++) { \
+                        tmp[ch] = tsrc[map[ch]]; \
+                    } \
+                    for (int ch = 0; ch < channels; ch++) { \
+                        tdst[ch] = tmp[ch]; \
+                    } \
                 } \
+                SDL_small_free(tmp, isstack); \
             } \
         } \
     }
@@ -199,8 +202,8 @@ static void SwizzleAudio(const int num_frames, void *dst, const void *src, int c
 // we also handle gain adjustment here, so we don't have to make another pass over the data later.
 // Strictly speaking, this is also a "conversion".  :)
 void ConvertAudio(int num_frames,
-                  const void *src, SDL_AudioFormat src_format, int src_channels, const Uint8 *src_map,
-                  void *dst, SDL_AudioFormat dst_format, int dst_channels, const Uint8 *dst_map,
+                  const void *src, SDL_AudioFormat src_format, int src_channels, const int *src_map,
+                  void *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
                   void* scratch, float gain)
 {
     SDL_assert(src != NULL);
@@ -374,9 +377,9 @@ static Sint64 GetAudioStreamResampleRate(SDL_AudioStream* stream, int src_freq,
     return resample_rate;
 }
 
-static int UpdateAudioStreamInputSpec(SDL_AudioStream *stream, const SDL_AudioSpec *spec)
+static int UpdateAudioStreamInputSpec(SDL_AudioStream *stream, const SDL_AudioSpec *spec, const int *chmap)
 {
-    if (SDL_AudioSpecsEqual(&stream->input_spec, spec)) {
+    if (SDL_AudioSpecsEqual(&stream->input_spec, spec, stream->input_chmap, chmap)) {
         return 0;
     }
 
@@ -384,6 +387,14 @@ static int UpdateAudioStreamInputSpec(SDL_AudioStream *stream, const SDL_AudioSp
         return -1;
     }
 
+    if (!chmap) {
+        stream->input_chmap = NULL;
+    } else {
+        const size_t chmaplen = sizeof (*chmap) * spec->channels;
+        stream->input_chmap = stream->input_chmap_storage;
+        SDL_memcpy(stream->input_chmap, chmap, chmaplen);
+    }
+
     SDL_copyp(&stream->input_spec, spec);
 
     return 0;
@@ -524,8 +535,6 @@ int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_s
             return SDL_SetError("Source rate is too low");
         } else if (src_spec->freq > max_freq) {
             return SDL_SetError("Source rate is too high");
-        } else if (src_spec->use_channel_map && SDL_ChannelMapIsBogus(src_spec->channel_map, src_spec->channels)) {
-            return SDL_SetError("Source channel map is invalid");
         }
     }
 
@@ -540,8 +549,6 @@ int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_s
             return SDL_SetError("Destination rate is too low");
         } else if (dst_spec->freq > max_freq) {
             return SDL_SetError("Destination rate is too high");
-        } else if (dst_spec->use_channel_map && SDL_ChannelMapIsBogus(dst_spec->channel_map, dst_spec->channels)) {
-            return SDL_SetError("Destination channel map is invalid");
         }
     }
 
@@ -557,27 +564,114 @@ int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_s
     }
 
     if (src_spec) {
-        SDL_copyp(&stream->src_spec, src_spec);
-        if (src_spec->use_channel_map && SDL_ChannelMapIsDefault(src_spec->channel_map, src_spec->channels)) {
-            stream->src_spec.use_channel_map = SDL_FALSE;  // turn off the channel map, as this is just unnecessary work.
+        if (src_spec->channels != stream->src_spec.channels) {
+            SDL_FreeLater(stream->src_chmap);  // this pointer is handed to the app during SDL_GetAudioStreamInputChannelMap
+            stream->src_chmap = NULL;
         }
+        SDL_copyp(&stream->src_spec, src_spec);
     }
 
     if (dst_spec) {
-        SDL_copyp(&stream->dst_spec, dst_spec);
-        if (dst_spec->use_channel_map && !stream->src_spec.use_channel_map && SDL_ChannelMapIsDefault(dst_spec->channel_map, dst_spec->channels)) {
-            stream->dst_spec.use_channel_map = SDL_FALSE;  // turn off the channel map, as this is just unnecessary work.
+        if (dst_spec->channels != stream->dst_spec.channels) {
+            SDL_FreeLater(stream->dst_chmap);  // this pointer is handed to the app during SDL_GetAudioStreamInputChannelMap
+            stream->dst_chmap = NULL;
         }
+        SDL_copyp(&stream->dst_spec, dst_spec);
     }
 
-    // !!! FIXME: decide if the source and dest channel maps would swizzle us back to the starting order and just turn them both off.
-    // !!! FIXME:  (but in this case, you can only do it if the channel count isn't changing, because source order is important to that.)
-
     SDL_UnlockMutex(stream->lock);
 
     return 0;
 }
 
+static int SetAudioStreamChannelMap(SDL_AudioStream *stream, const SDL_AudioSpec *spec, int **stream_chmap, const int *chmap, int channels, SDL_bool isinput)
+{
+    if (!stream) {
+        return SDL_InvalidParamError("stream");
+    }
+
+    int retval = 0;
+
+    SDL_LockMutex(stream->lock);
+
+    if (channels != spec->channels) {
+        retval = SDL_SetError("Wrong number of channels");
+    } else if (!*stream_chmap && !chmap) {
+        // already at default, we're good.
+    } else if (*stream_chmap && chmap && (SDL_memcmp(*stream_chmap, chmap, sizeof (*chmap) * channels) == 0)) {
+        // already have this map, don't allocate/copy it again.
+    } else if (SDL_ChannelMapIsBogus(chmap, channels)) {
+        retval = SDL_SetError("Invalid channel mapping");
+    } else if (stream->bound_device && (!!isinput == !!stream->bound_device->physical_device->recording)) {
+        // quietly refuse to change the format of the end currently bound to a device.
+    } else {
+        if (SDL_ChannelMapIsDefault(chmap, channels)) {
+            chmap = NULL;  // just apply a default mapping.
+        }
+        if (chmap) {
+            int *dupmap = SDL_ChannelMapDup(chmap, channels);
+            if (!dupmap) {
+                retval = SDL_SetError("Invalid channel mapping");
+            } else {
+                SDL_FreeLater(*stream_chmap);  // this pointer is handed to the app during SDL_GetAudioStreamInputChannelMap
+                *stream_chmap = dupmap;
+            }
+        } else {
+            SDL_FreeLater(*stream_chmap);  // this pointer is handed to the app during SDL_GetAudioStreamInputChannelMap
+            *stream_chmap = NULL;
+        }
+    }
+
+    SDL_UnlockMutex(stream->lock);
+    return retval;
+}
+
+int SDL_SetAudioStreamInputChannelMap(SDL_AudioStream *stream, const int *chmap, int channels)
+{
+    return SetAudioStreamChannelMap(stream, &stream->src_spec, &stream->src_chmap, chmap, channels, SDL_TRUE);
+}
+
+int SDL_SetAudioStreamOutputChannelMap(SDL_AudioStream *stream, const int *chmap, int channels)
+{
+    return SetAudioStreamChannelMap(stream, &stream->dst_spec, &stream->dst_chmap, chmap, channels, SDL_FALSE);
+}
+
+const int *SDL_GetAudioStreamInputChannelMap(SDL_AudioStream *stream, int *count)
+{
+    const int *retval = NULL;
+    int channels = 0;
+    if (stream) {
+        SDL_LockMutex(stream->lock);
+        retval = stream->src_chmap;
+        channels = stream->src_spec.channels;
+        SDL_UnlockMutex(stream->lock);
+    }
+
+    if (count) {
+        *count = channels;
+    }
+
+    return retval;
+}
+
+const int *SDL_GetAudioStreamOutputChannelMap(SDL_AudioStream *stream, int *count)
+{
+    const int *retval = NULL;
+    int channels = 0;
+    if (stream) {
+        SDL_LockMutex(stream->lock);
+        retval = stream->dst_chmap;
+        channels = stream->dst_spec.channels;
+        SDL_UnlockMutex(stream->lock);
+    }
+
+    if (count) {
+        *count = channels;
+    }
+
+    return retval;
+}
+
 float SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream)
 {
     if (!stream) {
@@ -676,7 +770,7 @@ static int PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int le
     SDL_AudioTrack* track = NULL;
 
     if (callback) {
-        track = SDL_CreateAudioTrack(stream->queue, &stream->src_spec, (Uint8 *)buf, len, len, callback, userdata);
+        track = SDL_CreateAudioTrack(stream->queue, &stream->src_spec, stream->src_chmap, (Uint8 *)buf, len, len, callback, userdata);
 
         if (!track) {
             SDL_UnlockMutex(stream->lock);
@@ -691,7 +785,7 @@ static int PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int le
     if (track) {
         SDL_AddTrackToAudioQueue(stream->queue, track);
     } else {
-        retval = SDL_WriteToAudioQueue(stream->queue, &stream->src_spec, (const Uint8 *)buf, len);
+        retval = SDL_WriteToAudioQueue(stream->queue, &stream->src_spec, stream->src_chmap, (const Uint8 *)buf, len);
     }
 
     if (retval == 0) {
@@ -782,16 +876,21 @@ static Uint8 *EnsureAudioStreamWorkBufferSize(SDL_AudioStream *stream, size_t ne
 }
 
 static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter,
-    Sint64* inout_resample_offset, SDL_AudioSpec* out_spec, SDL_bool* out_flushed)
+    Sint64* inout_resample_offset, SDL_AudioSpec* out_spec, int **out_chmap, SDL_bool* out_flushed)
 {
     SDL_AudioSpec spec;
     SDL_bool flushed;
-    size_t queued_bytes = SDL_NextAudioQueueIter(stream->queue, inout_iter, &spec, &flushed);
+    int *chmap;
+    size_t queued_bytes = SDL_NextAudioQueueIter(stream->queue, inout_iter, &spec, &chmap, &flushed);
 
     if (out_spec) {
         SDL_copyp(out_spec, &spec);
     }
 
+    if (out_chmap) {
+        *out_chmap = chmap;
+    }
+
     // There is infinite audio available, whether or not we are resampling
     if (queued_bytes == SDL_SIZE_MAX) {
         *inout_resample_offset = 0;
@@ -839,7 +938,7 @@ static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream* stream, Sint64* out
     Sint64 output_frames = 0;
 
     while (iter) {
-        output_frames += NextAudioStreamIter(stream, &iter, &resample_offset, NULL, NULL);
+        output_frames += NextAudioStreamIter(stream, &iter, &resample_offset, NULL, NULL, NULL);
 
         // Already got loads of frames. Just clamp it to something reasonable
         if (output_frames >= SDL_MAX_SINT32) {
@@ -855,7 +954,7 @@ static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream* stream, Sint64* out
     return output_frames;
 }
 
-static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spec, SDL_bool* out_flushed)
+static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spec, int **out_chmap, SDL_bool* out_flushed)
 {
     void* iter = SDL_BeginAudioQueueIter(stream->queue);
 
@@ -866,7 +965,7 @@ static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spe
     }
 
     Sint64 resample_offset = stream->resample_offset;
-    return NextAudioStreamIter(stream, &iter, &resample_offset, out_spec, out_flushed);
+    return NextAudioStreamIter(stream, &iter, &resample_offset, out_spec, out_chmap, out_flushed);
 }
 
 // You must hold stream->lock and validate your parameters before calling this!
@@ -881,7 +980,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou
 
     const SDL_AudioFormat dst_format = dst_spec->format;
     const int dst_channels = dst_spec->channels;
-    const Uint8 *dst_map = dst_spec->use_channel_map ? dst_spec->channel_map : NULL;
+    const int *dst_map = stream->dst_chmap;
 
     const int max_frame_size = CalculateMaxFrameSize(src_format, src_channels, dst_format, dst_channels);
     const Sint64 resample_rate = GetAudioStreamResampleRate(stream, src_spec->freq, stream->resample_offset);
@@ -1061,21 +1160,23 @@ int SDL_GetAudioStreamDataAdjustGain(SDL_AudioStream *stream, void *voidbuf, int
     while (total < len) {
         // Audio is processed a track at a time.
         SDL_AudioSpec input_spec;
+        int *input_chmap;
         SDL_bool flushed;
-        const Sint64 available_frames = GetAudioStreamHead(stream, &input_spec, &flushed);
+        const Sint64 available_frames = GetAudioStreamHead(stream, &input_spec, &input_chmap, &flushed);
 
         if (available_frames == 0) {
             if (flushed) {
                 SDL_PopAudioQueueHead(stream->queue);
                 SDL_zero(stream->input_spec);
                 stream->resample_offset = 0;
+                stream->input_chmap = NULL;
                 continue;
             }
             // There are no frames available, but the track hasn't been flushed, so more might be added later.
             break;
         }
 
-        if (UpdateAudioStreamInputSpec(stream, &input_spec) != 0) {
+        if (UpdateAudioStreamInputSpec(stream, &input_spec, input_chmap) != 0) {
             total = total ? total : -1;
             break;
         }
@@ -1160,6 +1261,7 @@ int SDL_ClearAudioStream(SDL_AudioStream *stream)
 
     SDL_ClearAudioQueue(stream->queue);
     SDL_zero(stream->input_spec);
+    stream->input_chmap = NULL;
     stream->resample_offset = 0;
 
     SDL_UnlockMutex(stream->lock);

+ 25 - 13
src/audio/SDL_audioqueue.c

@@ -36,6 +36,7 @@ struct SDL_MemoryPool
 struct SDL_AudioTrack
 {
     SDL_AudioSpec spec;
+    int *chmap;
     SDL_bool flushed;
     SDL_AudioTrack *next;
 
@@ -46,6 +47,8 @@ struct SDL_AudioTrack
     size_t head;
     size_t tail;
     size_t capacity;
+
+    int chmap_storage[SDL_MAX_CHANNELMAP_CHANNELS];  // !!! FIXME: this needs to grow if SDL ever supports more channels. But if it grows, we should probably be more clever about allocations.
 };
 
 struct SDL_AudioQueue
@@ -226,7 +229,7 @@ void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
 }
 
 SDL_AudioTrack *SDL_CreateAudioTrack(
-    SDL_AudioQueue *queue, const SDL_AudioSpec *spec,
+    SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap,
     Uint8 *data, size_t len, size_t capacity,
     SDL_ReleaseAudioBufferCallback callback, void *userdata)
 {
@@ -237,6 +240,13 @@ SDL_AudioTrack *SDL_CreateAudioTrack(
     }
 
     SDL_zerop(track);
+
+    if (chmap) {
+        SDL_assert(SDL_arraysize(track->chmap_storage) >= spec->channels);
+        SDL_memcpy(track->chmap_storage, chmap, sizeof (*chmap) * spec->channels);
+        track->chmap = track->chmap_storage;
+    }
+
     SDL_copyp(&track->spec, spec);
 
     track->userdata = userdata;
@@ -256,7 +266,7 @@ static void SDLCALL FreeChunkedAudioBuffer(void *userdata, const void *buf, int
     FreeMemoryPoolBlock(&queue->chunk_pool, (void *)buf);
 }
 
-static SDL_AudioTrack *CreateChunkedAudioTrack(SDL_AudioQueue *queue, const SDL_AudioSpec *spec)
+static SDL_AudioTrack *CreateChunkedAudioTrack(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap)
 {
     void *chunk = AllocMemoryPoolBlock(&queue->chunk_pool);
 
@@ -267,7 +277,7 @@ static SDL_AudioTrack *CreateChunkedAudioTrack(SDL_AudioQueue *queue, const SDL_
     size_t capacity = queue->chunk_pool.block_size;
     capacity -= capacity % SDL_AUDIO_FRAMESIZE(*spec);
 
-    SDL_AudioTrack *track = SDL_CreateAudioTrack(queue, spec, chunk, 0, capacity, FreeChunkedAudioBuffer, queue);
+    SDL_AudioTrack *track = SDL_CreateAudioTrack(queue, spec, chmap, chunk, 0, capacity, FreeChunkedAudioBuffer, queue);
 
     if (!track) {
         FreeMemoryPoolBlock(&queue->chunk_pool, chunk);
@@ -283,7 +293,7 @@ void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track)
 
     if (tail) {
         // If the spec has changed, make sure to flush the previous track
-        if (!SDL_AudioSpecsEqual(&tail->spec, &track->spec)) {
+        if (!SDL_AudioSpecsEqual(&tail->spec, &track->spec, tail->chmap, track->chmap)) {
             FlushAudioTrack(tail);
         }
 
@@ -308,7 +318,7 @@ static size_t WriteToAudioTrack(SDL_AudioTrack *track, const Uint8 *data, size_t
     return len;
 }
 
-int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len)
+int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap, const Uint8 *data, size_t len)
 {
     if (len == 0) {
         return 0;
@@ -317,12 +327,12 @@ int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, cons
     SDL_AudioTrack *track = queue->tail;
 
     if (track) {
-        if (!SDL_AudioSpecsEqual(&track->spec, spec)) {
+        if (!SDL_AudioSpecsEqual(&track->spec, spec, track->chmap, chmap)) {
             FlushAudioTrack(track);
         }
     } else {
         SDL_assert(!queue->head);
-        track = CreateChunkedAudioTrack(queue, spec);
+        track = CreateChunkedAudioTrack(queue, spec, chmap);
 
         if (!track) {
             return -1;
@@ -333,7 +343,7 @@ int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, cons
     }
 
     for (;;) {
-        size_t written = WriteToAudioTrack(track, data, len);
+        const size_t written = WriteToAudioTrack(track, data, len);
         data += written;
         len -= written;
 
@@ -341,7 +351,7 @@ int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, cons
             break;
         }
 
-        SDL_AudioTrack *new_track = CreateChunkedAudioTrack(queue, spec);
+        SDL_AudioTrack *new_track = CreateChunkedAudioTrack(queue, spec, chmap);
 
         if (!new_track) {
             return -1;
@@ -360,12 +370,13 @@ void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue)
     return queue->head;
 }
 
-size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed)
+size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, int **out_chmap, SDL_bool *out_flushed)
 {
     SDL_AudioTrack *iter = (SDL_AudioTrack *)(*inout_iter);
     SDL_assert(iter != NULL);
 
     SDL_copyp(out_spec, &iter->spec);
+    *out_chmap = iter->chmap;
 
     SDL_bool flushed = SDL_FALSE;
     size_t queued_bytes = 0;
@@ -512,7 +523,7 @@ static const Uint8 *PeekIntoAudioQueueFuture(SDL_AudioQueue *queue, Uint8 *data,
 }
 
 const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
-                                    Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const Uint8 *dst_map,
+                                    Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
                                     int past_frames, int present_frames, int future_frames,
                                     Uint8 *scratch, float gain)
 {
@@ -524,7 +535,7 @@ const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
 
     SDL_AudioFormat src_format = track->spec.format;
     int src_channels = track->spec.channels;
-    const Uint8 *src_map = track->spec.use_channel_map ? track->spec.channel_map : NULL;
+    const int *src_map = track->chmap;
 
     size_t src_frame_size = SDL_AUDIO_BYTESIZE(src_format) * src_channels;
     size_t dst_frame_size = SDL_AUDIO_BYTESIZE(dst_format) * dst_channels;
@@ -597,9 +608,10 @@ size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue)
 
     while (iter) {
         SDL_AudioSpec src_spec;
+        int *src_chmap;
         SDL_bool flushed;
 
-        size_t avail = SDL_NextAudioQueueIter(queue, &iter, &src_spec, &flushed);
+        size_t avail = SDL_NextAudioQueueIter(queue, &iter, &src_spec, &src_chmap, &flushed);
 
         if (avail >= SDL_SIZE_MAX - total) {
             total = SDL_SIZE_MAX;

+ 4 - 4
src/audio/SDL_audioqueue.h

@@ -48,11 +48,11 @@ void SDL_PopAudioQueueHead(SDL_AudioQueue *queue);
 
 // Write data to the end of queue
 // REQUIRES: If the spec has changed, the last track must have been flushed
-int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len);
+int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap, const Uint8 *data, size_t len);
 
 // Create a track where the input data is owned by the caller
 SDL_AudioTrack *SDL_CreateAudioTrack(SDL_AudioQueue *queue,
-                                     const SDL_AudioSpec *spec, Uint8 *data, size_t len, size_t capacity,
+                                     const SDL_AudioSpec *spec, const int *chmap, Uint8 *data, size_t len, size_t capacity,
                                      SDL_ReleaseAudioBufferCallback callback, void *userdata);
 
 // Add a track to the end of the queue
@@ -64,10 +64,10 @@ void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue);
 
 // Query and update the track iterator
 // REQUIRES: `*inout_iter != NULL` (a valid iterator)
-size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed);
+size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, int **out_chmap, SDL_bool *out_flushed);
 
 const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
-                                    Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const Uint8 *dst_map,
+                                    Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
                                     int past_frames, int present_frames, int future_frames,
                                     Uint8 *scratch, float gain);
 

+ 18 - 6
src/audio/SDL_sysaudio.h

@@ -48,6 +48,8 @@
 #define DEFAULT_AUDIO_RECORDING_CHANNELS 1
 #define DEFAULT_AUDIO_RECORDING_FREQUENCY 44100
 
+#define SDL_MAX_CHANNELMAP_CHANNELS 8  // !!! FIXME: if SDL ever supports more channels, clean this out and make those parts dynamic.
+
 typedef struct SDL_AudioDevice SDL_AudioDevice;
 typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice;
 
@@ -111,18 +113,22 @@ extern void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SD
 extern void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt);
 extern void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize);
 
-extern SDL_bool SDL_ChannelMapIsDefault(const Uint8 *map, int channels);
-extern SDL_bool SDL_ChannelMapIsBogus(const Uint8 *map, int channels);
+extern SDL_bool SDL_ChannelMapIsDefault(const int *map, int channels);
+extern SDL_bool SDL_ChannelMapIsBogus(const int *map, int channels);
 
 // this gets used from the audio device threads. It has rules, don't use this if you don't know how to use it!
 extern void ConvertAudio(int num_frames,
-                         const void *src, SDL_AudioFormat src_format, int src_channels, const Uint8 *src_map,
-                         void *dst, SDL_AudioFormat dst_format, int dst_channels, const Uint8 *dst_map,
+                         const void *src, SDL_AudioFormat src_format, int src_channels, const int *src_map,
+                         void *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
                          void* scratch, float gain);
 
 // Compare two SDL_AudioSpecs, return SDL_TRUE if they match exactly.
-// Using SDL_memcmp directly isn't safe, since potential padding (and unused parts of the channel map) might not be initialized.
-extern SDL_bool SDL_AudioSpecsEqual(const SDL_AudioSpec *a, const SDL_AudioSpec *b);
+// Using SDL_memcmp directly isn't safe, since potential padding might not be initialized.
+// either channel maps can be NULL for the default (and both should be if you don't care about them).
+extern SDL_bool SDL_AudioSpecsEqual(const SDL_AudioSpec *a, const SDL_AudioSpec *b, const int *channel_map_a, const int *channel_map_b);
+
+// allocate+copy a channel map.
+extern int *SDL_ChannelMapDup(const int *origchmap, int channels);
 
 // Special case to let something in SDL_audiocvt.c access something in SDL_audio.c. Don't use this.
 extern void OnAudioStreamCreated(SDL_AudioStream *stream);
@@ -197,12 +203,16 @@ struct SDL_AudioStream
 
     SDL_AudioSpec src_spec;
     SDL_AudioSpec dst_spec;
+    int *src_chmap;
+    int *dst_chmap;
     float freq_ratio;
     float gain;
 
     struct SDL_AudioQueue* queue;
 
     SDL_AudioSpec input_spec; // The spec of input data currently being processed
+    int *input_chmap;
+    int input_chmap_storage[SDL_MAX_CHANNELMAP_CHANNELS];  // !!! FIXME: this needs to grow if SDL ever supports more channels. But if it grows, we should probably be more clever about allocations.
     Sint64 resample_offset;
 
     Uint8 *work_buffer;    // used for scratch space during data conversion/resampling.
@@ -288,6 +298,8 @@ struct SDL_AudioDevice
     SDL_AudioSpec spec;
     int buffer_size;
 
+    int *chmap;
+
     // The device's default audio specification
     SDL_AudioSpec default_spec;
 

+ 27 - 19
src/audio/alsa/SDL_alsa_audio.c

@@ -249,13 +249,13 @@ static const char *get_audio_device(void *handle, const int channels)
 // https://bugzilla.libsdl.org/show_bug.cgi?id=110
 //  "For Linux ALSA, this is FL-FR-RL-RR-C-LFE
 //  and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR"
-static const Uint8 swizzle_alsa_channels_6[6] = { 0, 1, 4, 5, 2, 3 };
+static const int swizzle_alsa_channels_6[6] = { 0, 1, 4, 5, 2, 3 };
 
 // 7.1 swizzle:
 // https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/mapping-stream-formats-to-speaker-configurations
 //  For Linux ALSA, this appears to be FL-FR-RL-RR-C-LFE-SL-SR
 //  and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-SL-SR-RL-RR"
-static const Uint8 swizzle_alsa_channels_8[8] = { 0, 1, 6, 7, 2, 3, 4, 5 };
+static const int swizzle_alsa_channels_8[8] = { 0, 1, 6, 7, 2, 3, 4, 5 };
 
 
 
@@ -533,33 +533,41 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device)
         device->spec.channels = channels;
     }
 
-    // Validate number of channels and determine if swizzling is necessary.
-    //  Assume original swizzling, until proven otherwise.
+    const int *swizmap = NULL;
     if (channels == 6) {
-        device->spec.use_channel_map = SDL_TRUE;
-        SDL_memcpy(device->spec.channel_map, swizzle_alsa_channels_6, sizeof (device->spec.channel_map[0]) * channels);
+        swizmap = swizzle_alsa_channels_6;
     } else if (channels == 8) {
-        device->spec.use_channel_map = SDL_TRUE;
-        SDL_memcpy(device->spec.channel_map, swizzle_alsa_channels_8, sizeof (device->spec.channel_map[0]) * channels);
+        swizmap = swizzle_alsa_channels_8;
     }
 
 #ifdef SND_CHMAP_API_VERSION
-    snd_pcm_chmap_t *chmap = ALSA_snd_pcm_get_chmap(pcm_handle);
-    if (chmap) {
-        char chmap_str[64];
-        if (ALSA_snd_pcm_chmap_print(chmap, sizeof(chmap_str), chmap_str) > 0) {
-            if ( (channels == 6) &&
-                 ((SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0) ||
-                  (SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0)) ) {
-                device->spec.use_channel_map = SDL_FALSE;
-            } else if ((channels == 8) && (SDL_strcmp("FL FR FC LFE SL SR RL RR", chmap_str) == 0)) {
-                device->spec.use_channel_map = SDL_FALSE;
+    if (swizmap) {
+        snd_pcm_chmap_t *chmap = ALSA_snd_pcm_get_chmap(pcm_handle);
+        if (chmap) {
+            char chmap_str[64];
+            if (ALSA_snd_pcm_chmap_print(chmap, sizeof(chmap_str), chmap_str) > 0) {
+                if ( (channels == 6) &&
+                     ((SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0) ||
+                      (SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0)) ) {
+                    swizmap = NULL;
+                } else if ((channels == 8) && (SDL_strcmp("FL FR FC LFE SL SR RL RR", chmap_str) == 0)) {
+                    swizmap = NULL;
+                }
             }
+            free(chmap); // This should NOT be SDL_free()
         }
-        free(chmap); // This should NOT be SDL_free()
     }
 #endif // SND_CHMAP_API_VERSION
 
+    // Validate number of channels and determine if swizzling is necessary.
+    //  Assume original swizzling, until proven otherwise.
+    if (swizmap) {
+        device->chmap = SDL_ChannelMapDup(swizmap, channels);
+        if (!device->chmap) {
+            return -1;
+        }
+    }
+
     // Set the audio rate
     unsigned int rate = device->spec.freq;
     status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams,

+ 5 - 0
src/dynapi/SDL_dynapi.sym

@@ -165,6 +165,7 @@ SDL3_0.0.0 {
     SDL_GetAndroidSDKVersion;
     SDL_GetAssertionHandler;
     SDL_GetAssertionReport;
+    SDL_GetAudioDeviceChannelMap;
     SDL_GetAudioDeviceFormat;
     SDL_GetAudioDeviceGain;
     SDL_GetAudioDeviceName;
@@ -177,6 +178,8 @@ SDL3_0.0.0 {
     SDL_GetAudioStreamFormat;
     SDL_GetAudioStreamFrequencyRatio;
     SDL_GetAudioStreamGain;
+    SDL_GetAudioStreamInputChannelMap;
+    SDL_GetAudioStreamOutputChannelMap;
     SDL_GetAudioStreamProperties;
     SDL_GetAudioStreamQueued;
     SDL_GetBasePath;
@@ -693,6 +696,8 @@ SDL3_0.0.0 {
     SDL_SetAudioStreamFrequencyRatio;
     SDL_SetAudioStreamGain;
     SDL_SetAudioStreamGetCallback;
+    SDL_SetAudioStreamInputChannelMap;
+    SDL_SetAudioStreamOutputChannelMap;
     SDL_SetAudioStreamPutCallback;
     SDL_SetBooleanProperty;
     SDL_SetClipboardData;

+ 5 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -189,6 +189,7 @@
 #define SDL_GetAndroidSDKVersion SDL_GetAndroidSDKVersion_REAL
 #define SDL_GetAssertionHandler SDL_GetAssertionHandler_REAL
 #define SDL_GetAssertionReport SDL_GetAssertionReport_REAL
+#define SDL_GetAudioDeviceChannelMap SDL_GetAudioDeviceChannelMap_REAL
 #define SDL_GetAudioDeviceFormat SDL_GetAudioDeviceFormat_REAL
 #define SDL_GetAudioDeviceGain SDL_GetAudioDeviceGain_REAL
 #define SDL_GetAudioDeviceName SDL_GetAudioDeviceName_REAL
@@ -201,6 +202,8 @@
 #define SDL_GetAudioStreamFormat SDL_GetAudioStreamFormat_REAL
 #define SDL_GetAudioStreamFrequencyRatio SDL_GetAudioStreamFrequencyRatio_REAL
 #define SDL_GetAudioStreamGain SDL_GetAudioStreamGain_REAL
+#define SDL_GetAudioStreamInputChannelMap SDL_GetAudioStreamInputChannelMap_REAL
+#define SDL_GetAudioStreamOutputChannelMap SDL_GetAudioStreamOutputChannelMap_REAL
 #define SDL_GetAudioStreamProperties SDL_GetAudioStreamProperties_REAL
 #define SDL_GetAudioStreamQueued SDL_GetAudioStreamQueued_REAL
 #define SDL_GetBasePath SDL_GetBasePath_REAL
@@ -717,6 +720,8 @@
 #define SDL_SetAudioStreamFrequencyRatio SDL_SetAudioStreamFrequencyRatio_REAL
 #define SDL_SetAudioStreamGain SDL_SetAudioStreamGain_REAL
 #define SDL_SetAudioStreamGetCallback SDL_SetAudioStreamGetCallback_REAL
+#define SDL_SetAudioStreamInputChannelMap SDL_SetAudioStreamInputChannelMap_REAL
+#define SDL_SetAudioStreamOutputChannelMap SDL_SetAudioStreamOutputChannelMap_REAL
 #define SDL_SetAudioStreamPutCallback SDL_SetAudioStreamPutCallback_REAL
 #define SDL_SetBooleanProperty SDL_SetBooleanProperty_REAL
 #define SDL_SetClipboardData SDL_SetClipboardData_REAL

+ 5 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -208,6 +208,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_GamepadSensorEnabled,(SDL_Gamepad *a, SDL_SensorTyp
 SDL_DYNAPI_PROC(int,SDL_GetAndroidSDKVersion,(void),(),return)
 SDL_DYNAPI_PROC(SDL_AssertionHandler,SDL_GetAssertionHandler,(void **a),(a),return)
 SDL_DYNAPI_PROC(const SDL_AssertData*,SDL_GetAssertionReport,(void),(),return)
+SDL_DYNAPI_PROC(const int*,SDL_GetAudioDeviceChannelMap,(SDL_AudioDeviceID a, int *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceFormat,(SDL_AudioDeviceID a, SDL_AudioSpec *b, int *c),(a,b,c),return)
 SDL_DYNAPI_PROC(float,SDL_GetAudioDeviceGain,(SDL_AudioDeviceID a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetAudioDeviceName,(SDL_AudioDeviceID a),(a),return)
@@ -220,6 +221,8 @@ SDL_DYNAPI_PROC(SDL_AudioDeviceID,SDL_GetAudioStreamDevice,(SDL_AudioStream *a),
 SDL_DYNAPI_PROC(int,SDL_GetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioSpec *b, SDL_AudioSpec *c),(a,b,c),return)
 SDL_DYNAPI_PROC(float,SDL_GetAudioStreamFrequencyRatio,(SDL_AudioStream *a),(a),return)
 SDL_DYNAPI_PROC(float,SDL_GetAudioStreamGain,(SDL_AudioStream *a),(a),return)
+SDL_DYNAPI_PROC(const int*,SDL_GetAudioStreamInputChannelMap,(SDL_AudioStream *a, int *b),(a,b),return)
+SDL_DYNAPI_PROC(const int*,SDL_GetAudioStreamOutputChannelMap,(SDL_AudioStream *a, int *b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetAudioStreamProperties,(SDL_AudioStream *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_GetAudioStreamQueued,(SDL_AudioStream *a),(a),return)
 SDL_DYNAPI_PROC(char*,SDL_GetBasePath,(void),(),return)
@@ -727,6 +730,8 @@ SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, const SDL_Audi
 SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFrequencyRatio,(SDL_AudioStream *a, float b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetAudioStreamGain,(SDL_AudioStream *a, float b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetAudioStreamGetCallback,(SDL_AudioStream *a, SDL_AudioStreamCallback b, void *c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_SetAudioStreamInputChannelMap,(SDL_AudioStream *a, const int *b, int c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_SetAudioStreamOutputChannelMap,(SDL_AudioStream *a, const int *b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SetAudioStreamPutCallback,(SDL_AudioStream *a, SDL_AudioStreamCallback b, void *c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SetBooleanProperty,(SDL_PropertiesID a, const char *b, SDL_bool c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SetClipboardData,(SDL_ClipboardDataCallback a, SDL_ClipboardCleanupCallback b, void *c, const char **d, size_t e),(a,b,c,d,e),return)

+ 1 - 0
test/loopwave.c

@@ -114,6 +114,7 @@ int SDL_AppInit(void **appstate, int argc, char *argv[])
         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create audio stream: %s\n", SDL_GetError());
         return SDL_APP_FAILURE;
     }
+
     SDL_ResumeAudioStreamDevice(stream);
 
     return SDL_APP_CONTINUE;

+ 2 - 2
test/testffmpeg.c

@@ -1167,7 +1167,7 @@ static AVCodecContext *OpenAudioStream(AVFormatContext *ic, int stream, const AV
         return NULL;
     }
 
-    SDL_AudioSpec spec = { SDL_AUDIO_F32, codecpar->ch_layout.nb_channels, codecpar->sample_rate, SDL_FALSE };
+    SDL_AudioSpec spec = { SDL_AUDIO_F32, codecpar->ch_layout.nb_channels, codecpar->sample_rate };
     audio = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL);
     if (audio) {
         SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(audio));
@@ -1240,7 +1240,7 @@ static void InterleaveAudio(AVFrame *frame, const SDL_AudioSpec *spec)
 static void HandleAudioFrame(AVFrame *frame)
 {
     if (audio) {
-        SDL_AudioSpec spec = { GetAudioFormat(frame->format), frame->ch_layout.nb_channels, frame->sample_rate, SDL_FALSE };
+        SDL_AudioSpec spec = { GetAudioFormat(frame->format), frame->ch_layout.nb_channels, frame->sample_rate };
         SDL_SetAudioStreamFormat(audio, &spec, NULL);
 
         if (frame->ch_layout.nb_channels > 1 && IsPlanarAudioFormat(frame->format)) {