diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index d8879e2aa9..92251c92fa 100644 --- a/include/SDL3/SDL_audio.h +++ b/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); diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 4a3c7281f6..9d63bcfaaf 100644 --- a/src/audio/SDL_audio.c +++ b/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(¤t_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(¤t_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. } diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index 29cd63dc6d..3da7543ceb 100644 --- a/src/audio/SDL_audiocvt.c +++ b/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); diff --git a/src/audio/SDL_audioqueue.c b/src/audio/SDL_audioqueue.c index e9110b5ac0..29b4077930 100644 --- a/src/audio/SDL_audioqueue.c +++ b/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; diff --git a/src/audio/SDL_audioqueue.h b/src/audio/SDL_audioqueue.h index 46dce2d19b..88d6972f1b 100644 --- a/src/audio/SDL_audioqueue.h +++ b/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); diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index b5c2335e2c..20ca908574 100644 --- a/src/audio/SDL_sysaudio.h +++ b/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; diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c index 07a4849709..e2827f418d 100644 --- a/src/audio/alsa/SDL_alsa_audio.c +++ b/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, diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index d42716d858..79aec801d9 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/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; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 0c593d688a..6b1a00ae28 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/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 diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 7d03e3bf8a..51be63cac9 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/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) diff --git a/test/loopwave.c b/test/loopwave.c index 9cd24fae9e..45bc060c83 100644 --- a/test/loopwave.c +++ b/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; diff --git a/test/testffmpeg.c b/test/testffmpeg.c index c414bef569..0adbb23e38 100644 --- a/test/testffmpeg.c +++ b/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)) {