mirror of
https://github.com/libsdl-org/SDL.git
synced 2025-05-28 15:39:10 +00:00
audio: Separate channel maps out of SDL_AudioSpec.
This commit is contained in:
parent
1664ac4fcb
commit
4755055bc3
12 changed files with 455 additions and 114 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
||||
|
|
|
@ -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,9 +162,9 @@ 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); \
|
||||
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]]; \
|
||||
|
@ -172,6 +173,8 @@ static void SwizzleAudio(const int num_frames, void *dst, const void *src, int c
|
|||
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) {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,17 +533,15 @@ 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
|
||||
if (swizmap) {
|
||||
snd_pcm_chmap_t *chmap = ALSA_snd_pcm_get_chmap(pcm_handle);
|
||||
if (chmap) {
|
||||
char chmap_str[64];
|
||||
|
@ -551,15 +549,25 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device)
|
|||
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;
|
||||
swizmap = NULL;
|
||||
} else if ((channels == 8) && (SDL_strcmp("FL FR FC LFE SL SR RL RR", chmap_str) == 0)) {
|
||||
device->spec.use_channel_map = SDL_FALSE;
|
||||
swizmap = NULL;
|
||||
}
|
||||
}
|
||||
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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue