diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 3f8b9d918..f2e7c0916 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -43,20 +43,41 @@ * if you aren't reading from a file) as a basic means to load sound data into * your program. * - * For multi-channel audio, data is interleaved (one sample for each channel, - * then repeat). The SDL channel order is: + * ## Channel layouts as SDL expects them * - * - Stereo: FL, FR - * - 2.1 surround: FL, FR, LFE - * - Quad: FL, FR, BL, BR - * - 4.1 surround: FL, FR, LFE, BL, BR - * - 5.1 surround: FL, FR, FC, LFE, SL, SR (last two can also be BL BR) - * - 6.1 surround: FL, FR, FC, LFE, BC, SL, SR - * - 7.1 surround: FL, FR, FC, LFE, BL, BR, SL, SR + * Abbreviations: + * + * - FRONT = single mono speaker + * - FL = front left speaker + * - FR = front right speaker + * - FC = front center speaker + * - BL = back left speaker + * - BR = back right speaker + * - SR = surround right speaker + * - SL = surround left speaker + * - BC = back center speaker + * - LFE = low-frequency speaker + * + * These are listed in the order they are laid out in + * memory, so "FL, FR" means "the front left speaker is + * laid out in memory first, then the front right, then + * it repeats for the next audio frame". + * + * - 1 channel (mono) layout: FRONT + * - 2 channels (stereo) layout: FL, FR + * - 3 channels (2.1) layout: FL, FR, LFE + * - 4 channels (quad) layout: FL, FR, BL, BR + * - 5 channels (4.1) layout: FL, FR, LFE, BL, BR + * - 6 channels (5.1) layout: FL, FR, FC, LFE, BL, BR (last two can also be BL, BR) + * - 7 channels (6.1) layout: FL, FR, FC, LFE, BC, SL, SR + * - 8 channels (7.1) layout: FL, FR, FC, LFE, BL, BR, SL, SR * * This is the same order as DirectSound expects, but applied to all * 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 + * to whatever is necessary, in other audio processing scenarios. */ #ifndef SDL_audio_h_ @@ -280,6 +301,18 @@ 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. * @@ -292,6 +325,8 @@ 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; /** @@ -318,6 +353,7 @@ typedef struct SDL_AudioSpec * when it doesn't have the complete buffer available. * - It can handle incoming data in any variable size. * - It can handle input/output format changes on the fly. + * - It can remap audio channels between inputs and outputs. * - You push data as you have it, and pull it when you need it * - It can also function as a basic audio data queue even if you just have * sound that needs to pass from one place to another. diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 5194858bf..0c1edacda 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -249,6 +249,16 @@ static void UpdateAudioStreamFormatsPhysical(SDL_AudioDevice *device) } } +SDL_bool SDL_AudioSpecsEqual(const SDL_AudioSpec *a, const SDL_AudioSpec *b) +{ + if ((a->format != b->format) || (a->channels != b->channels) || (a->freq != b->freq) || (a->use_channel_map != b->use_channel_map)) { + 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)) { + return SDL_FALSE; + } + return SDL_TRUE; +} + // Zombie device implementation... @@ -632,11 +642,13 @@ SDL_AudioDevice *SDL_AddAudioDevice(SDL_bool recording, const char *name, const const int default_freq = recording ? DEFAULT_AUDIO_RECORDING_FREQUENCY : DEFAULT_AUDIO_PLAYBACK_FREQUENCY; SDL_AudioSpec spec; + SDL_zero(spec); if (!inspec) { spec.format = default_format; 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; @@ -1089,7 +1101,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(AUDIO_SPECS_EQUAL(stream->dst_spec, device->spec)); + SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &device->spec)); const int br = SDL_AtomicGet(&logdev->paused) ? 0 : SDL_GetAudioStreamData(stream, device_buffer, buffer_size); if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow. @@ -1106,9 +1118,8 @@ SDL_bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) SDL_assert(work_buffer_size <= device->work_buffer_size); + SDL_copyp(&outspec, &device->spec); outspec.format = SDL_AUDIO_F32; - outspec.channels = device->spec.channels; - outspec.freq = device->spec.freq; SDL_memset(final_mix_buffer, '\0', work_buffer_size); // start with silence. @@ -1126,7 +1137,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(AUDIO_SPECS_EQUAL(stream->dst_spec, outspec)); + SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &outspec)); /* 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. @@ -1150,8 +1161,8 @@ SDL_bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device) if (((Uint8 *) final_mix_buffer) != device_buffer) { // !!! FIXME: we can't promise the device buf is aligned/padded for SIMD. - //ConvertAudio(needed_samples * device->spec.channels, final_mix_buffer, SDL_AUDIO_F32, device->spec.channels, device_buffer, device->spec.format, device->spec.channels, device->work_buffer); - ConvertAudio(needed_samples / device->spec.channels, final_mix_buffer, SDL_AUDIO_F32, device->spec.channels, device->work_buffer, device->spec.format, device->spec.channels, NULL); + //ConvertAudio(needed_samples / device->spec.channels, final_mix_buffer, SDL_AUDIO_F32, device->spec.channels, NULL, device_buffer, device->spec.format, device->spec.channels, NULL, NULL); + ConvertAudio(needed_samples / device->spec.channels, final_mix_buffer, SDL_AUDIO_F32, device->spec.channels, NULL, device->work_buffer, device->spec.format, device->spec.channels, NULL, NULL); SDL_memcpy(device_buffer, device->work_buffer, buffer_size); } } @@ -1242,13 +1253,12 @@ SDL_bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device) if (logdev->postmix) { // move to float format. SDL_AudioSpec outspec; + SDL_copyp(&outspec, &device->spec); outspec.format = SDL_AUDIO_F32; - outspec.channels = device->spec.channels; - outspec.freq = device->spec.freq; output_buffer = device->postmix_buffer; const int frames = br / SDL_AUDIO_FRAMESIZE(device->spec); br = frames * SDL_AUDIO_FRAMESIZE(outspec); - ConvertAudio(frames, device->work_buffer, device->spec.format, outspec.channels, device->postmix_buffer, SDL_AUDIO_F32, outspec.channels, NULL); + ConvertAudio(frames, device->work_buffer, device->spec.format, outspec.channels, NULL, device->postmix_buffer, SDL_AUDIO_F32, outspec.channels, NULL, NULL); logdev->postmix(logdev->postmix_userdata, &outspec, device->postmix_buffer, br); } @@ -1606,6 +1616,7 @@ 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. @@ -2155,7 +2166,7 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) } if (needs_migration) { - const SDL_bool spec_changed = !AUDIO_SPECS_EQUAL(current_default_device->spec, new_default_device->spec); + const SDL_bool spec_changed = !SDL_AudioSpecsEqual(¤t_default_device->spec, &new_default_device->spec); SDL_LogicalAudioDevice *next = NULL; for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev; logdev = next) { next = logdev->next; @@ -2235,7 +2246,7 @@ int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL { const int orig_work_buffer_size = device->work_buffer_size; - if (AUDIO_SPECS_EQUAL(device->spec, *newspec) && new_sample_frames == device->sample_frames) { + if (SDL_AudioSpecsEqual(&device->spec, newspec) && (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 638f57096..6b7a9e566 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -29,39 +29,6 @@ #define SDL_INT_MAX ((int)(~0u>>1)) #endif -/* - * CHANNEL LAYOUTS AS SDL EXPECTS THEM: - * - * (Even if the platform expects something else later, that - * SDL will swizzle between the app and the platform). - * - * Abbreviations: - * - FRONT=single mono speaker - * - FL=front left speaker - * - FR=front right speaker - * - FC=front center speaker - * - BL=back left speaker - * - BR=back right speaker - * - SR=surround right speaker - * - SL=surround left speaker - * - BC=back center speaker - * - LFE=low-frequency speaker - * - * These are listed in the order they are laid out in - * memory, so "FL+FR" means "the front left speaker is - * laid out in memory first, then the front right, then - * it repeats for the next audio frame". - * - * 1 channel (mono) layout: FRONT - * 2 channels (stereo) layout: FL+FR - * 3 channels (2.1) layout: FL+FR+LFE - * 4 channels (quad) layout: FL+FR+BL+BR - * 5 channels (4.1) layout: FL+FR+LFE+BL+BR - * 6 channels (5.1) layout: FL+FR+FC+LFE+BL+BR - * 7 channels (6.1) layout: FL+FR+FC+LFE+BC+SL+SR - * 8 channels (7.1) layout: FL+FR+FC+LFE+BL+BR+SL+SR - */ - #ifdef SDL_SSE3_INTRINSICS // Convert from stereo to mono. Average left and right. static void SDL_TARGETING("sse3") SDL_ConvertStereoToMono_SSE3(float *dst, const float *src, int num_frames) @@ -157,6 +124,68 @@ static SDL_bool SDL_IsSupportedChannelCount(const int channels) return ((channels >= 1) && (channels <= 8)); } +SDL_bool SDL_ChannelMapIsBogus(const Uint8 *map, int channels) +{ + if (map) { + for (int i = 0; i < channels; i++) { + if (map[i] >= ((Uint8) channels)) { + return SDL_TRUE; + } + } + } + return SDL_FALSE; +} + +SDL_bool SDL_ChannelMapIsDefault(const Uint8 *map, int channels) +{ + if (map) { + for (int i = 0; i < channels; i++) { + if (map[i] != i) { + return SDL_FALSE; + } + } + } + return SDL_TRUE; +} + +// 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) +{ + #define CHANNEL_SWIZZLE(bits) { \ + Uint##bits *tdst = (Uint##bits *) dst; /* treat as UintX; we only care about moving bits and not the type here. */ \ + const Uint##bits *tsrc = (const Uint##bits *) src; \ + if (src != dst) { /* don't need to copy to a temporary frame first. */ \ + for (int i = 0; i < num_frames; i++, tsrc += channels, tdst += channels) { \ + for (int ch = 0; ch < channels; ch++) { \ + tdst[ch] = tsrc[map[ch]]; \ + } \ + } \ + } else { \ + Uint##bits tmp[SDL_MAX_CHANNEL_MAP_SIZE]; \ + 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]; \ + } \ + } \ + } \ + } + + switch (bitsize) { + case 8: CHANNEL_SWIZZLE(8); break; + case 16: CHANNEL_SWIZZLE(16); break; + case 32: CHANNEL_SWIZZLE(32); break; + // we don't currently have int64 or double audio datatypes, so no `case 64` for now. + default: SDL_assert(!"Unsupported audio datatype size"); break; + } + + #undef CHANNEL_SWIZZLE +} + + // This does type and channel conversions _but not resampling_ (resampling happens in SDL_AudioStream). // This does not check parameter validity, (beyond asserts), it expects you did that already! // All of this has to function as if src==dst==scratch (conversion in-place), but as a convenience @@ -164,8 +193,10 @@ static SDL_bool SDL_IsSupportedChannelCount(const int channels) // // The scratch buffer must be able to store `num_frames * CalculateMaxSampleFrameSize(src_format, src_channels, dst_format, dst_channels)` bytes. // If the scratch buffer is NULL, this restriction applies to the output buffer instead. -void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, int src_channels, - void *dst, SDL_AudioFormat dst_format, int dst_channels, void* scratch) +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, + void* scratch) { SDL_assert(src != NULL); SDL_assert(dst != NULL); @@ -188,11 +219,13 @@ void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, i const int dst_sample_frame_size = (dst_bitsize / 8) * dst_channels; /* Type conversion goes like this now: + - swizzle through source channel map to "standard" layout. - byteswap to CPU native format first if necessary. - convert to native Float32 if necessary. - change channel count if necessary. - convert to final data format. - byteswap back to foreign format if necessary. + - swizzle through dest channel map from "standard" layout. The expectation is we can process data faster in float32 (possibly with SIMD), and making several passes over the same @@ -201,11 +234,20 @@ void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, i (script-generated) custom converters for every data type and it was a bloat on SDL compile times and final library size. */ + // swizzle input to "standard" format if necessary. + if (src_map) { + void* buf = scratch ? scratch : dst; // use scratch if available, since it has to be big enough to hold src, unless it's NULL, then dst has to be. + SwizzleAudio(num_frames, buf, src, src_channels, src_map, src_bitsize); + src = buf; + } + // see if we can skip float conversion entirely. if (src_channels == dst_channels) { if (src_format == dst_format) { // nothing to do, we're already in the right format, just copy it over if necessary. - if (src != dst) { + if (dst_map) { + SwizzleAudio(num_frames, dst, src, dst_channels, dst_map, dst_bitsize); + } else if (src != dst) { SDL_memcpy(dst, src, num_frames * dst_sample_frame_size); } return; @@ -213,7 +255,11 @@ void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, i // just a byteswap needed? if ((src_format ^ dst_format) == SDL_AUDIO_MASK_BIG_ENDIAN) { - ConvertAudioSwapEndian(dst, src, num_frames * src_channels, src_bitsize); + if (dst_map) { // do this first, in case we duplicate channels, we can avoid an extra copy if src != dst. + SwizzleAudio(num_frames, dst, src, dst_channels, dst_map, dst_bitsize); + src = dst; + } + ConvertAudioSwapEndian(dst, src, num_frames * dst_channels, dst_bitsize); return; // all done. } } @@ -275,6 +321,10 @@ void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, i } SDL_assert(src == dst); // if we got here, we _had_ to have done _something_. Otherwise, we should have memcpy'd! + + if (dst_map) { + SwizzleAudio(num_frames, dst, src, dst_channels, dst_map, dst_bitsize); + } } // Calculate the largest frame size needed to convert between the two formats. @@ -304,7 +354,7 @@ static Sint64 GetAudioStreamResampleRate(SDL_AudioStream* stream, int src_freq, static int UpdateAudioStreamInputSpec(SDL_AudioStream *stream, const SDL_AudioSpec *spec) { - if (AUDIO_SPECS_EQUAL(stream->input_spec, *spec)) { + if (SDL_AudioSpecsEqual(&stream->input_spec, spec)) { return 0; } @@ -451,6 +501,8 @@ 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"); } } @@ -465,6 +517,8 @@ 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"); } } @@ -481,12 +535,21 @@ 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 (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. + } } + // !!! 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; @@ -766,6 +829,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 max_frame_size = CalculateMaxFrameSize(src_format, src_channels, dst_format, dst_channels); const Sint64 resample_rate = GetAudioStreamResampleRate(stream, src_spec->freq, stream->resample_offset); @@ -789,7 +853,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou } } - if (SDL_ReadFromAudioQueue(stream->queue, buf, dst_format, dst_channels, 0, output_frames, 0, work_buffer) != buf) { + if (SDL_ReadFromAudioQueue(stream->queue, buf, dst_format, dst_channels, dst_map, 0, output_frames, 0, work_buffer) != buf) { return SDL_SetError("Not enough data in queue"); } @@ -854,8 +918,9 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou return -1; } + // (dst channel map is NULL because we'll do the final swizzle on ConvertAudio after resample.) const Uint8* input_buffer = SDL_ReadFromAudioQueue(stream->queue, - NULL, resample_format, resample_channels, + NULL, resample_format, resample_channels, NULL, padding_frames, input_frames, padding_frames, work_buffer); if (!input_buffer) { @@ -872,8 +937,8 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou (float*) resample_buffer, output_frames, resample_rate, &stream->resample_offset); - // Convert to the final format, if necessary - ConvertAudio(output_frames, resample_buffer, resample_format, resample_channels, buf, dst_format, dst_channels, work_buffer); + // Convert to the final format, if necessary (src channel map is NULL because SDL_ReadFromAudioQueue already handled this). + ConvertAudio(output_frames, resample_buffer, resample_format, resample_channels, NULL, buf, dst_format, dst_channels, dst_map, work_buffer); return 0; } diff --git a/src/audio/SDL_audioqueue.c b/src/audio/SDL_audioqueue.c index 53ddddb17..41c8af3b4 100644 --- a/src/audio/SDL_audioqueue.c +++ b/src/audio/SDL_audioqueue.c @@ -23,8 +23,6 @@ #include "SDL_audioqueue.h" #include "SDL_sysaudio.h" -#define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq)) - typedef struct SDL_MemoryPool SDL_MemoryPool; struct SDL_MemoryPool @@ -285,7 +283,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 (!AUDIO_SPECS_EQUAL(tail->spec, track->spec)) { + if (!SDL_AudioSpecsEqual(&tail->spec, &track->spec)) { FlushAudioTrack(tail); } @@ -319,7 +317,7 @@ int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, cons SDL_AudioTrack *track = queue->tail; if (track) { - if (!AUDIO_SPECS_EQUAL(track->spec, *spec)) { + if (!SDL_AudioSpecsEqual(&track->spec, spec)) { FlushAudioTrack(track); } } else { @@ -514,7 +512,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, + Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const Uint8 *dst_map, int past_frames, int present_frames, int future_frames, Uint8 *scratch) { @@ -526,6 +524,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; size_t src_frame_size = SDL_AUDIO_BYTESIZE(src_format) * src_channels; size_t dst_frame_size = SDL_AUDIO_BYTESIZE(dst_format) * dst_channels; @@ -553,7 +552,7 @@ const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, // Do we still need to copy/convert the data? if (dst) { ConvertAudio(past_frames + present_frames + future_frames, ptr, - src_format, src_channels, dst, dst_format, dst_channels, scratch); + src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch); ptr = dst; } @@ -571,19 +570,19 @@ const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *ptr = dst; if (src_past_bytes) { - ConvertAudio(past_frames, PeekIntoAudioQueuePast(queue, scratch, src_past_bytes), src_format, src_channels, dst, dst_format, dst_channels, scratch); + ConvertAudio(past_frames, PeekIntoAudioQueuePast(queue, scratch, src_past_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch); dst += dst_past_bytes; scratch += dst_past_bytes; } if (src_present_bytes) { - ConvertAudio(present_frames, ReadFromAudioQueue(queue, scratch, src_present_bytes), src_format, src_channels, dst, dst_format, dst_channels, scratch); + ConvertAudio(present_frames, ReadFromAudioQueue(queue, scratch, src_present_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch); dst += dst_present_bytes; scratch += dst_present_bytes; } if (src_future_bytes) { - ConvertAudio(future_frames, PeekIntoAudioQueueFuture(queue, scratch, src_future_bytes), src_format, src_channels, dst, dst_format, dst_channels, scratch); + ConvertAudio(future_frames, PeekIntoAudioQueueFuture(queue, scratch, src_future_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch); dst += dst_future_bytes; scratch += dst_future_bytes; } diff --git a/src/audio/SDL_audioqueue.h b/src/audio/SDL_audioqueue.h index 54c2ba72a..26675ce29 100644 --- a/src/audio/SDL_audioqueue.h +++ b/src/audio/SDL_audioqueue.h @@ -67,7 +67,7 @@ void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue); size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed); const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, - Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, + Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const Uint8 *dst_map, int past_frames, int present_frames, int future_frames, Uint8 *scratch); diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 4c3d2e6d0..9973be9bf 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -48,8 +48,6 @@ #define DEFAULT_AUDIO_RECORDING_CHANNELS 1 #define DEFAULT_AUDIO_RECORDING_FREQUENCY 44100 -#define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq)) - typedef struct SDL_AudioDevice SDL_AudioDevice; typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice; @@ -113,9 +111,19 @@ 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); + // 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, - void *dst, SDL_AudioFormat dst_format, int dst_channels, void* scratch); +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, + void* scratch); + +// 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); + // 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); diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c index 115707f48..07a484970 100644 --- a/src/audio/alsa/SDL_alsa_audio.c +++ b/src/audio/alsa/SDL_alsa_audio.c @@ -242,108 +242,22 @@ static const char *get_audio_device(void *handle, const int channels) return dev->name; } -// !!! FIXME: is there a channel swizzler in alsalib instead? +// Swizzle channels to match SDL defaults. +// These are swizzles _from_ SDL's layouts to what ALSA wants. +// 5.1 swizzle: // 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" -#define SWIZ6(T) \ - static void swizzle_alsa_channels_6_##T(void *buffer, const Uint32 bufferlen) \ - { \ - T *ptr = (T *)buffer; \ - Uint32 i; \ - for (i = 0; i < bufferlen; i++, ptr += 6) { \ - T tmp; \ - tmp = ptr[2]; \ - ptr[2] = ptr[4]; \ - ptr[4] = tmp; \ - tmp = ptr[3]; \ - ptr[3] = ptr[5]; \ - ptr[5] = tmp; \ - } \ - } - - -// !!! FIXME: is there a channel swizzler in alsalib instead? -// !!! FIXME: this screams for a SIMD shuffle operation. +static const Uint8 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" -#define SWIZ8(T) \ - static void swizzle_alsa_channels_8_##T(void *buffer, const Uint32 bufferlen) \ - { \ - T *ptr = (T *)buffer; \ - Uint32 i; \ - for (i = 0; i < bufferlen; i++, ptr += 6) { \ - const T center = ptr[2]; \ - const T subwoofer = ptr[3]; \ - const T side_left = ptr[4]; \ - const T side_right = ptr[5]; \ - const T rear_left = ptr[6]; \ - const T rear_right = ptr[7]; \ - ptr[2] = rear_left; \ - ptr[3] = rear_right; \ - ptr[4] = center; \ - ptr[5] = subwoofer; \ - ptr[6] = side_left; \ - ptr[7] = side_right; \ - } \ - } +static const Uint8 swizzle_alsa_channels_8[8] = { 0, 1, 6, 7, 2, 3, 4, 5 }; -#define CHANNEL_SWIZZLE(x) \ - x(Uint64) \ - x(Uint32) \ - x(Uint16) \ - x(Uint8) -CHANNEL_SWIZZLE(SWIZ6) -CHANNEL_SWIZZLE(SWIZ8) - -#undef CHANNEL_SWIZZLE -#undef SWIZ6 -#undef SWIZ8 - -// Called right before feeding device->hidden->mixbuf to the hardware. Swizzle -// channels from Windows/Mac order to the format alsalib will want. -static void swizzle_alsa_channels(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen) -{ - switch (device->spec.channels) { -#define CHANSWIZ(chans) \ - case chans: \ - switch ((device->spec.format & (0xFF))) { \ - case 8: \ - swizzle_alsa_channels_##chans##_Uint8(buffer, bufferlen); \ - break; \ - case 16: \ - swizzle_alsa_channels_##chans##_Uint16(buffer, bufferlen); \ - break; \ - case 32: \ - swizzle_alsa_channels_##chans##_Uint32(buffer, bufferlen); \ - break; \ - case 64: \ - swizzle_alsa_channels_##chans##_Uint64(buffer, bufferlen); \ - break; \ - default: \ - SDL_assert(!"unhandled bitsize"); \ - break; \ - } \ - return; - - CHANSWIZ(6); - CHANSWIZ(8); -#undef CHANSWIZ - default: - break; - } -} - -#ifdef SND_CHMAP_API_VERSION -// Some devices have the right channel map, no swizzling necessary -static void no_swizzle(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen) -{ -} -#endif // SND_CHMAP_API_VERSION // This function waits until it is possible to write a full sound buffer static int ALSA_WaitDevice(SDL_AudioDevice *device) @@ -380,8 +294,6 @@ static int ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buf const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec); snd_pcm_uframes_t frames_left = (snd_pcm_uframes_t) (buflen / frame_size); - device->hidden->swizzle_func(device, sample_buf, frames_left); - while ((frames_left > 0) && !SDL_AtomicGet(&device->shutdown)) { const int rc = ALSA_snd_pcm_writei(device->hidden->pcm_handle, sample_buf, frames_left); //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA PLAYDEVICE: WROTE %d of %d bytes", (rc >= 0) ? ((int) (rc * frame_size)) : rc, (int) (frames_left * frame_size)); @@ -447,8 +359,6 @@ static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) return -1; } return 0; // go back to WaitDevice and try again. - } else if (rc > 0) { - device->hidden->swizzle_func(device, buffer, total_frames - rc); } //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: recorded %d bytes", rc * frame_size); @@ -611,23 +521,6 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device) } device->spec.format = test_format; - // Validate number of channels and determine if swizzling is necessary. - // Assume original swizzling, until proven otherwise. - device->hidden->swizzle_func = swizzle_alsa_channels; -#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 (SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0 || - SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0) { - device->hidden->swizzle_func = no_swizzle; - } - } - free(chmap); // This should NOT be SDL_free() - } -#endif // SND_CHMAP_API_VERSION - // Set the number of channels status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams, device->spec.channels); @@ -640,6 +533,33 @@ 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. + 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); + } 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); + } + +#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; + } + } + free(chmap); // This should NOT be SDL_free() + } +#endif // SND_CHMAP_API_VERSION + // 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/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 8bfb0e576..6a9681fd5 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -789,6 +789,7 @@ static SDL_AudioFormat PulseFormatToSDLFormat(pa_sample_format_t format) static void AddPulseAudioDevice(const SDL_bool recording, const char *description, const char *name, const uint32_t index, const pa_sample_spec *sample_spec) { SDL_AudioSpec spec; + SDL_zero(spec); spec.format = PulseFormatToSDLFormat(sample_spec->format); spec.channels = sample_spec->channels; spec.freq = sample_spec->rate; diff --git a/src/audio/qnx/SDL_qsa_audio.c b/src/audio/qnx/SDL_qsa_audio.c index 531ec4036..42e9c0e13 100644 --- a/src/audio/qnx/SDL_qsa_audio.c +++ b/src/audio/qnx/SDL_qsa_audio.c @@ -363,6 +363,7 @@ static void QSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevic if (status == EOK) { SDL_AudioSpec spec; + SDL_zero(spec); SDL_AudioSpec *pspec = &spec; snd_pcm_channel_setup_t csetup; SDL_zero(csetup); diff --git a/test/testaudiostreamdynamicresample.c b/test/testaudiostreamdynamicresample.c index a0a949e62..58aeb4c96 100644 --- a/test/testaudiostreamdynamicresample.c +++ b/test/testaudiostreamdynamicresample.c @@ -109,6 +109,7 @@ static void queue_audio() int retval = 0; SDL_AudioSpec new_spec; + SDL_zero(new_spec); new_spec.format = spec.format; new_spec.channels = (int) sliders[2].value; new_spec.freq = (int) sliders[1].value; diff --git a/test/testautomation_audio.c b/test/testautomation_audio.c index aa2fb02d7..a505efda7 100644 --- a/test/testautomation_audio.c +++ b/test/testautomation_audio.c @@ -181,7 +181,7 @@ static int audio_initOpenCloseQuitAudio(void *arg) SDLTest_AssertCheck(result == 0, "Validate result value; expected: 0 got: %d", result); /* Set spec */ - SDL_memset(&desired, 0, sizeof(desired)); + SDL_zero(desired); switch (j) { case 0: /* Set standard desired spec */ @@ -272,7 +272,7 @@ static int audio_pauseUnpauseAudio(void *arg) SDLTest_AssertCheck(result == 0, "Validate result value; expected: 0 got: %d", result); /* Set spec */ - SDL_memset(&desired, 0, sizeof(desired)); + SDL_zero(desired); switch (j) { case 0: /* Set standard desired spec */ @@ -496,6 +496,9 @@ static int audio_buildAudioStream(void *arg) SDL_AudioSpec spec2; int i, ii, j, jj, k, kk; + SDL_zero(spec1); + SDL_zero(spec2); + /* Call Quit */ SDL_QuitSubSystem(SDL_INIT_AUDIO); SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_AUDIO)"); @@ -567,6 +570,9 @@ static int audio_buildAudioStreamNegative(void *arg) int i; char message[256]; + SDL_zero(spec1); + SDL_zero(spec2); + /* Valid format */ spec1.format = SDL_AUDIO_S8; spec1.channels = 1; @@ -678,6 +684,9 @@ static int audio_convertAudio(void *arg) char message[128]; int i, ii, j, jj, k, kk; + SDL_zero(spec1); + SDL_zero(spec2); + /* Iterate over bitmask that determines which parameters are modified in the conversion */ for (c = 1; c < 8; c++) { SDL_strlcpy(message, "Changing:", 128); @@ -995,6 +1004,9 @@ static int audio_resampleLoss(void *arg) double sum_squared_value = 0; double signal_to_noise = 0; + SDL_zero(tmpspec1); + SDL_zero(tmpspec2); + SDLTest_AssertPass("Test resampling of %i s %i Hz %f phase sine wave from sampling rate of %i Hz to %i Hz", spec->time, spec->freq, spec->phase, spec->rate_in, spec->rate_out); @@ -1147,6 +1159,9 @@ static int audio_convertAccuracy(void *arg) int tmp_len, dst_len; int ret; + SDL_zero(src_spec); + SDL_zero(tmp_spec); + SDL_AudioFormat format = formats[i]; const char* format_name = format_names[i]; @@ -1238,6 +1253,10 @@ static int audio_formatChange(void *arg) double target_signal_to_noise = 75.0; int sine_freq = 500; + SDL_zero(spec1); + SDL_zero(spec2); + SDL_zero(spec3); + spec1.format = SDL_AUDIO_F32; spec1.channels = 1; spec1.freq = 20000; diff --git a/test/testffmpeg.c b/test/testffmpeg.c index 3826be652..3d9cca666 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_AudioSpec spec = { SDL_AUDIO_F32, codecpar->ch_layout.nb_channels, codecpar->sample_rate, SDL_FALSE }; 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_AudioSpec spec = { GetAudioFormat(frame->format), frame->ch_layout.nb_channels, frame->sample_rate, SDL_FALSE }; SDL_SetAudioStreamFormat(audio, &spec, NULL); if (frame->ch_layout.nb_channels > 1 && IsPlanarAudioFormat(frame->format)) {