1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_sysaudio.h"
24
25#include "SDL_audioqueue.h"
26#include "SDL_audioresample.h"
27
28#ifndef SDL_INT_MAX
29#define SDL_INT_MAX ((int)(~0u>>1))
30#endif
31
32#ifdef SDL_SSE3_INTRINSICS
33// Convert from stereo to mono. Average left and right.
34static void SDL_TARGETING("sse3") SDL_ConvertStereoToMono_SSE3(float *dst, const float *src, int num_frames)
35{
36 LOG_DEBUG_AUDIO_CONVERT("stereo", "mono (using SSE3)");
37
38 const __m128 divby2 = _mm_set1_ps(0.5f);
39 int i = num_frames;
40
41 /* Do SSE blocks as long as we have 16 bytes available.
42 Just use unaligned load/stores, if the memory at runtime is
43 aligned it'll be just as fast on modern processors */
44 while (i >= 4) { // 4 * float32
45 _mm_storeu_ps(dst, _mm_mul_ps(_mm_hadd_ps(_mm_loadu_ps(src), _mm_loadu_ps(src + 4)), divby2));
46 i -= 4;
47 src += 8;
48 dst += 4;
49 }
50
51 // Finish off any leftovers with scalar operations.
52 while (i) {
53 *dst = (src[0] + src[1]) * 0.5f;
54 dst++;
55 i--;
56 src += 2;
57 }
58}
59#endif
60
61#ifdef SDL_SSE_INTRINSICS
62// Convert from mono to stereo. Duplicate to stereo left and right.
63static void SDL_TARGETING("sse") SDL_ConvertMonoToStereo_SSE(float *dst, const float *src, int num_frames)
64{
65 LOG_DEBUG_AUDIO_CONVERT("mono", "stereo (using SSE)");
66
67 // convert backwards, since output is growing in-place.
68 src += (num_frames-4) * 1;
69 dst += (num_frames-4) * 2;
70
71 /* Do SSE blocks as long as we have 16 bytes available.
72 Just use unaligned load/stores, if the memory at runtime is
73 aligned it'll be just as fast on modern processors */
74 // convert backwards, since output is growing in-place.
75 int i = num_frames;
76 while (i >= 4) { // 4 * float32
77 const __m128 input = _mm_loadu_ps(src); // A B C D
78 _mm_storeu_ps(dst, _mm_unpacklo_ps(input, input)); // A A B B
79 _mm_storeu_ps(dst + 4, _mm_unpackhi_ps(input, input)); // C C D D
80 i -= 4;
81 src -= 4;
82 dst -= 8;
83 }
84
85 // Finish off any leftovers with scalar operations.
86 src += 3;
87 dst += 6; // adjust for smaller buffers.
88 while (i) { // convert backwards, since output is growing in-place.
89 const float srcFC = src[0];
90 dst[1] /* FR */ = srcFC;
91 dst[0] /* FL */ = srcFC;
92 i--;
93 src--;
94 dst -= 2;
95 }
96}
97#endif
98
99// Include the autogenerated channel converters...
100#include "SDL_audio_channel_converters.h"
101
102static bool SDL_IsSupportedAudioFormat(const SDL_AudioFormat fmt)
103{
104 switch (fmt) {
105 case SDL_AUDIO_U8:
106 case SDL_AUDIO_S8:
107 case SDL_AUDIO_S16LE:
108 case SDL_AUDIO_S16BE:
109 case SDL_AUDIO_S32LE:
110 case SDL_AUDIO_S32BE:
111 case SDL_AUDIO_F32LE:
112 case SDL_AUDIO_F32BE:
113 return true; // supported.
114
115 default:
116 break;
117 }
118
119 return false; // unsupported.
120}
121
122static bool SDL_IsSupportedChannelCount(const int channels)
123{
124 return ((channels >= 1) && (channels <= 8));
125}
126
127bool SDL_ChannelMapIsBogus(const int *chmap, int channels)
128{
129 if (chmap) {
130 for (int i = 0; i < channels; i++) {
131 const int mapping = chmap[i];
132 if ((mapping < -1) || (mapping >= channels)) {
133 return true;
134 }
135 }
136 }
137 return false;
138}
139
140bool SDL_ChannelMapIsDefault(const int *chmap, int channels)
141{
142 if (chmap) {
143 for (int i = 0; i < channels; i++) {
144 if (chmap[i] != i) {
145 return false;
146 }
147 }
148 }
149 return true;
150}
151
152// Swizzle audio channels. src and dst can be the same pointer. It does not change the buffer size.
153static void SwizzleAudio(const int num_frames, void *dst, const void *src, int channels, const int *map, SDL_AudioFormat fmt)
154{
155 const int bitsize = (int) SDL_AUDIO_BITSIZE(fmt);
156
157 bool has_null_mappings = false; // !!! FIXME: calculate this when setting the channel map instead.
158 for (int i = 0; i < channels; i++) {
159 if (map[i] == -1) {
160 has_null_mappings = true;
161 break;
162 }
163 }
164
165 #define CHANNEL_SWIZZLE(bits) { \
166 Uint##bits *tdst = (Uint##bits *) dst; /* treat as UintX; we only care about moving bits and not the type here. */ \
167 const Uint##bits *tsrc = (const Uint##bits *) src; \
168 if (src != dst) { /* don't need to copy to a temporary frame first. */ \
169 if (has_null_mappings) { \
170 const Uint##bits silence = (Uint##bits) SDL_GetSilenceValueForFormat(fmt); \
171 for (int i = 0; i < num_frames; i++, tsrc += channels, tdst += channels) { \
172 for (int ch = 0; ch < channels; ch++) { \
173 const int m = map[ch]; \
174 tdst[ch] = (m == -1) ? silence : tsrc[m]; \
175 } \
176 } \
177 } else { \
178 for (int i = 0; i < num_frames; i++, tsrc += channels, tdst += channels) { \
179 for (int ch = 0; ch < channels; ch++) { \
180 tdst[ch] = tsrc[map[ch]]; \
181 } \
182 } \
183 } \
184 } else { \
185 bool isstack; \
186 Uint##bits *tmp = (Uint##bits *) SDL_small_alloc(int, channels, &isstack); /* !!! FIXME: allocate this when setting the channel map instead. */ \
187 if (tmp) { \
188 if (has_null_mappings) { \
189 const Uint##bits silence = (Uint##bits) SDL_GetSilenceValueForFormat(fmt); \
190 for (int i = 0; i < num_frames; i++, tsrc += channels, tdst += channels) { \
191 for (int ch = 0; ch < channels; ch++) { \
192 const int m = map[ch]; \
193 tmp[ch] = (m == -1) ? silence : tsrc[m]; \
194 } \
195 for (int ch = 0; ch < channels; ch++) { \
196 tdst[ch] = tmp[ch]; \
197 } \
198 } \
199 } else { \
200 for (int i = 0; i < num_frames; i++, tsrc += channels, tdst += channels) { \
201 for (int ch = 0; ch < channels; ch++) { \
202 tmp[ch] = tsrc[map[ch]]; \
203 } \
204 for (int ch = 0; ch < channels; ch++) { \
205 tdst[ch] = tmp[ch]; \
206 } \
207 } \
208 } \
209 SDL_small_free(tmp, isstack); \
210 } \
211 } \
212 }
213
214 switch (bitsize) {
215 case 8: CHANNEL_SWIZZLE(8); break;
216 case 16: CHANNEL_SWIZZLE(16); break;
217 case 32: CHANNEL_SWIZZLE(32); break;
218 // we don't currently have int64 or double audio datatypes, so no `case 64` for now.
219 default: SDL_assert(!"Unsupported audio datatype size"); break;
220 }
221
222 #undef CHANNEL_SWIZZLE
223}
224
225
226// This does type and channel conversions _but not resampling_ (resampling happens in SDL_AudioStream).
227// This does not check parameter validity, (beyond asserts), it expects you did that already!
228// All of this has to function as if src==dst==scratch (conversion in-place), but as a convenience
229// if you're just going to copy the final output elsewhere, you can specify a different output pointer.
230//
231// The scratch buffer must be able to store `num_frames * CalculateMaxSampleFrameSize(src_format, src_channels, dst_format, dst_channels)` bytes.
232// If the scratch buffer is NULL, this restriction applies to the output buffer instead.
233//
234// Since this is a convenient point that audio goes through even if it doesn't need format conversion,
235// we also handle gain adjustment here, so we don't have to make another pass over the data later.
236// Strictly speaking, this is also a "conversion". :)
237void ConvertAudio(int num_frames,
238 const void *src, SDL_AudioFormat src_format, int src_channels, const int *src_map,
239 void *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
240 void *scratch, float gain)
241{
242 SDL_assert(src != NULL);
243 SDL_assert(dst != NULL);
244 SDL_assert(SDL_IsSupportedAudioFormat(src_format));
245 SDL_assert(SDL_IsSupportedAudioFormat(dst_format));
246 SDL_assert(SDL_IsSupportedChannelCount(src_channels));
247 SDL_assert(SDL_IsSupportedChannelCount(dst_channels));
248
249 if (!num_frames) {
250 return; // no data to convert, quit.
251 }
252
253#if DEBUG_AUDIO_CONVERT
254 SDL_Log("SDL_AUDIO_CONVERT: Convert format %04x->%04x, channels %u->%u", src_format, dst_format, src_channels, dst_channels);
255#endif
256
257 const int dst_bitsize = (int) SDL_AUDIO_BITSIZE(dst_format);
258 const int dst_sample_frame_size = (dst_bitsize / 8) * dst_channels;
259
260 const bool chmaps_match = (src_channels == dst_channels) && SDL_AudioChannelMapsEqual(src_channels, src_map, dst_map);
261 if (chmaps_match) {
262 src_map = dst_map = NULL; // NULL both these out so we don't do any unnecessary swizzling.
263 }
264
265 /* Type conversion goes like this now:
266 - swizzle through source channel map to "standard" layout.
267 - byteswap to CPU native format first if necessary.
268 - convert to native Float32 if necessary.
269 - change channel count if necessary.
270 - convert to final data format.
271 - byteswap back to foreign format if necessary.
272 - swizzle through dest channel map from "standard" layout.
273
274 The expectation is we can process data faster in float32
275 (possibly with SIMD), and making several passes over the same
276 buffer is likely to be CPU cache-friendly, avoiding the
277 biggest performance hit in modern times. Previously we had
278 (script-generated) custom converters for every data type and
279 it was a bloat on SDL compile times and final library size. */
280
281 // swizzle input to "standard" format if necessary.
282 if (src_map) {
283 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.
284 SwizzleAudio(num_frames, buf, src, src_channels, src_map, src_format);
285 src = buf;
286 }
287
288 // see if we can skip float conversion entirely.
289 if ((src_channels == dst_channels) && (gain == 1.0f)) {
290 if (src_format == dst_format) {
291 // nothing to do, we're already in the right format, just copy it over if necessary.
292 if (dst_map) {
293 SwizzleAudio(num_frames, dst, src, dst_channels, dst_map, dst_format);
294 } else if (src != dst) {
295 SDL_memcpy(dst, src, num_frames * dst_sample_frame_size);
296 }
297 return;
298 }
299
300 // just a byteswap needed?
301 if ((src_format ^ dst_format) == SDL_AUDIO_MASK_BIG_ENDIAN) {
302 if (dst_map) { // do this first, in case we duplicate channels, we can avoid an extra copy if src != dst.
303 SwizzleAudio(num_frames, dst, src, dst_channels, dst_map, dst_format);
304 src = dst;
305 }
306 ConvertAudioSwapEndian(dst, src, num_frames * dst_channels, dst_bitsize);
307 return; // all done.
308 }
309 }
310
311 if (!scratch) {
312 scratch = dst;
313 }
314
315 const bool srcconvert = src_format != SDL_AUDIO_F32;
316 const bool channelconvert = src_channels != dst_channels;
317 const bool dstconvert = dst_format != SDL_AUDIO_F32;
318
319 // get us to float format.
320 if (srcconvert) {
321 void* buf = (channelconvert || dstconvert) ? scratch : dst;
322 ConvertAudioToFloat((float *) buf, src, num_frames * src_channels, src_format);
323 src = buf;
324 }
325
326 // Gain adjustment
327 if (gain != 1.0f) {
328 float *buf = (float *)((channelconvert || dstconvert) ? scratch : dst);
329 const int total_samples = num_frames * src_channels;
330 if (src == buf) {
331 for (int i = 0; i < total_samples; i++) {
332 buf[i] *= gain;
333 }
334 } else {
335 float *fsrc = (float *)src;
336 for (int i = 0; i < total_samples; i++) {
337 buf[i] = fsrc[i] * gain;
338 }
339 }
340 src = buf;
341 }
342
343 // Channel conversion
344
345 if (channelconvert) {
346 SDL_AudioChannelConverter channel_converter;
347 SDL_AudioChannelConverter override = NULL;
348
349 // SDL_IsSupportedChannelCount should have caught these asserts, or we added a new format and forgot to update the table.
350 SDL_assert(src_channels <= SDL_arraysize(channel_converters));
351 SDL_assert(dst_channels <= SDL_arraysize(channel_converters[0]));
352
353 channel_converter = channel_converters[src_channels - 1][dst_channels - 1];
354 SDL_assert(channel_converter != NULL);
355
356 // swap in some SIMD versions for a few of these.
357 if (channel_converter == SDL_ConvertStereoToMono) {
358 #ifdef SDL_SSE3_INTRINSICS
359 if (!override && SDL_HasSSE3()) { override = SDL_ConvertStereoToMono_SSE3; }
360 #endif
361 } else if (channel_converter == SDL_ConvertMonoToStereo) {
362 #ifdef SDL_SSE_INTRINSICS
363 if (!override && SDL_HasSSE()) { override = SDL_ConvertMonoToStereo_SSE; }
364 #endif
365 }
366
367 if (override) {
368 channel_converter = override;
369 }
370
371 void* buf = dstconvert ? scratch : dst;
372 channel_converter((float *) buf, (const float *) src, num_frames);
373 src = buf;
374 }
375
376 // Resampling is not done in here. SDL_AudioStream handles that.
377
378 // Move to final data type.
379 if (dstconvert) {
380 ConvertAudioFromFloat(dst, (const float *) src, num_frames * dst_channels, dst_format);
381 src = dst;
382 }
383
384 SDL_assert(src == dst); // if we got here, we _had_ to have done _something_. Otherwise, we should have memcpy'd!
385
386 if (dst_map) {
387 SwizzleAudio(num_frames, dst, src, dst_channels, dst_map, dst_format);
388 }
389}
390
391// Calculate the largest frame size needed to convert between the two formats.
392static int CalculateMaxFrameSize(SDL_AudioFormat src_format, int src_channels, SDL_AudioFormat dst_format, int dst_channels)
393{
394 const int src_format_size = SDL_AUDIO_BYTESIZE(src_format);
395 const int dst_format_size = SDL_AUDIO_BYTESIZE(dst_format);
396 const int max_app_format_size = SDL_max(src_format_size, dst_format_size);
397 const int max_format_size = SDL_max(max_app_format_size, sizeof (float)); // ConvertAudio and ResampleAudio use floats.
398 const int max_channels = SDL_max(src_channels, dst_channels);
399 return max_format_size * max_channels;
400}
401
402static Sint64 GetAudioStreamResampleRate(SDL_AudioStream* stream, int src_freq, Sint64 resample_offset)
403{
404 src_freq = (int)((float)src_freq * stream->freq_ratio);
405
406 Sint64 resample_rate = SDL_GetResampleRate(src_freq, stream->dst_spec.freq);
407
408 // If src_freq == dst_freq, and we aren't between frames, don't resample
409 if ((resample_rate == 0x100000000) && (resample_offset == 0)) {
410 resample_rate = 0;
411 }
412
413 return resample_rate;
414}
415
416static bool UpdateAudioStreamInputSpec(SDL_AudioStream *stream, const SDL_AudioSpec *spec, const int *chmap)
417{
418 if (SDL_AudioSpecsEqual(&stream->input_spec, spec, stream->input_chmap, chmap)) {
419 return true;
420 }
421
422 if (!SDL_ResetAudioQueueHistory(stream->queue, SDL_GetResamplerHistoryFrames())) {
423 return false;
424 }
425
426 if (!chmap) {
427 stream->input_chmap = NULL;
428 } else {
429 const size_t chmaplen = sizeof (*chmap) * spec->channels;
430 stream->input_chmap = stream->input_chmap_storage;
431 SDL_memcpy(stream->input_chmap, chmap, chmaplen);
432 }
433
434 SDL_copyp(&stream->input_spec, spec);
435
436 return true;
437}
438
439SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec)
440{
441 SDL_ChooseAudioConverters();
442 SDL_SetupAudioResampler();
443
444 SDL_AudioStream *result = (SDL_AudioStream *)SDL_calloc(1, sizeof(SDL_AudioStream));
445 if (!result) {
446 return NULL;
447 }
448
449 result->freq_ratio = 1.0f;
450 result->gain = 1.0f;
451 result->queue = SDL_CreateAudioQueue(8192);
452
453 if (!result->queue) {
454 SDL_free(result);
455 return NULL;
456 }
457
458 result->lock = SDL_CreateMutex();
459 if (!result->lock) {
460 SDL_free(result->queue);
461 SDL_free(result);
462 return NULL;
463 }
464
465 OnAudioStreamCreated(result);
466
467 if (!SDL_SetAudioStreamFormat(result, src_spec, dst_spec)) {
468 SDL_DestroyAudioStream(result);
469 return NULL;
470 }
471
472 return result;
473}
474
475SDL_PropertiesID SDL_GetAudioStreamProperties(SDL_AudioStream *stream)
476{
477 if (!stream) {
478 SDL_InvalidParamError("stream");
479 return 0;
480 }
481 SDL_LockMutex(stream->lock);
482 if (stream->props == 0) {
483 stream->props = SDL_CreateProperties();
484 }
485 SDL_UnlockMutex(stream->lock);
486 return stream->props;
487}
488
489bool SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata)
490{
491 if (!stream) {
492 return SDL_InvalidParamError("stream");
493 }
494 SDL_LockMutex(stream->lock);
495 stream->get_callback = callback;
496 stream->get_callback_userdata = userdata;
497 SDL_UnlockMutex(stream->lock);
498 return true;
499}
500
501bool SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata)
502{
503 if (!stream) {
504 return SDL_InvalidParamError("stream");
505 }
506 SDL_LockMutex(stream->lock);
507 stream->put_callback = callback;
508 stream->put_callback_userdata = userdata;
509 SDL_UnlockMutex(stream->lock);
510 return true;
511}
512
513bool SDL_LockAudioStream(SDL_AudioStream *stream)
514{
515 if (!stream) {
516 return SDL_InvalidParamError("stream");
517 }
518 SDL_LockMutex(stream->lock);
519 return true;
520}
521
522bool SDL_UnlockAudioStream(SDL_AudioStream *stream)
523{
524 if (!stream) {
525 return SDL_InvalidParamError("stream");
526 }
527 SDL_UnlockMutex(stream->lock);
528 return true;
529}
530
531bool SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, SDL_AudioSpec *dst_spec)
532{
533 if (!stream) {
534 return SDL_InvalidParamError("stream");
535 }
536
537 SDL_LockMutex(stream->lock);
538 if (src_spec) {
539 SDL_copyp(src_spec, &stream->src_spec);
540 }
541 if (dst_spec) {
542 SDL_copyp(dst_spec, &stream->dst_spec);
543 }
544 SDL_UnlockMutex(stream->lock);
545
546 if (src_spec && src_spec->format == 0) {
547 return SDL_SetError("Stream has no source format");
548 } else if (dst_spec && dst_spec->format == 0) {
549 return SDL_SetError("Stream has no destination format");
550 }
551
552 return true;
553}
554
555bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec)
556{
557 if (!stream) {
558 return SDL_InvalidParamError("stream");
559 }
560
561 // note that while we've removed the maximum frequency checks, SDL _will_
562 // fail to resample to extremely high sample rates correctly. Really high,
563 // like 196608000Hz. File a bug. :P
564
565 if (src_spec) {
566 if (!SDL_IsSupportedAudioFormat(src_spec->format)) {
567 return SDL_InvalidParamError("src_spec->format");
568 } else if (!SDL_IsSupportedChannelCount(src_spec->channels)) {
569 return SDL_InvalidParamError("src_spec->channels");
570 } else if (src_spec->freq <= 0) {
571 return SDL_InvalidParamError("src_spec->freq");
572 }
573 }
574
575 if (dst_spec) {
576 if (!SDL_IsSupportedAudioFormat(dst_spec->format)) {
577 return SDL_InvalidParamError("dst_spec->format");
578 } else if (!SDL_IsSupportedChannelCount(dst_spec->channels)) {
579 return SDL_InvalidParamError("dst_spec->channels");
580 } else if (dst_spec->freq <= 0) {
581 return SDL_InvalidParamError("dst_spec->freq");
582 }
583 }
584
585 SDL_LockMutex(stream->lock);
586
587 // quietly refuse to change the format of the end currently bound to a device.
588 if (stream->bound_device) {
589 if (stream->bound_device->physical_device->recording) {
590 src_spec = NULL;
591 } else {
592 dst_spec = NULL;
593 }
594 }
595
596 if (src_spec) {
597 if (src_spec->channels != stream->src_spec.channels) {
598 SDL_free(stream->src_chmap);
599 stream->src_chmap = NULL;
600 }
601 SDL_copyp(&stream->src_spec, src_spec);
602 }
603
604 if (dst_spec) {
605 if (dst_spec->channels != stream->dst_spec.channels) {
606 SDL_free(stream->dst_chmap);
607 stream->dst_chmap = NULL;
608 }
609 SDL_copyp(&stream->dst_spec, dst_spec);
610 }
611
612 SDL_UnlockMutex(stream->lock);
613
614 return true;
615}
616
617bool SetAudioStreamChannelMap(SDL_AudioStream *stream, const SDL_AudioSpec *spec, int **stream_chmap, const int *chmap, int channels, int isinput)
618{
619 if (!stream) {
620 return SDL_InvalidParamError("stream");
621 }
622
623 bool result = true;
624
625 SDL_LockMutex(stream->lock);
626
627 if (channels != spec->channels) {
628 result = SDL_SetError("Wrong number of channels");
629 } else if (!*stream_chmap && !chmap) {
630 // already at default, we're good.
631 } else if (*stream_chmap && chmap && (SDL_memcmp(*stream_chmap, chmap, sizeof (*chmap) * channels) == 0)) {
632 // already have this map, don't allocate/copy it again.
633 } else if (SDL_ChannelMapIsBogus(chmap, channels)) {
634 result = SDL_SetError("Invalid channel mapping");
635 } else {
636 if (SDL_ChannelMapIsDefault(chmap, channels)) {
637 chmap = NULL; // just apply a default mapping.
638 }
639 if (chmap) {
640 int *dupmap = SDL_ChannelMapDup(chmap, channels);
641 if (!dupmap) {
642 result = SDL_SetError("Invalid channel mapping");
643 } else {
644 SDL_free(*stream_chmap);
645 *stream_chmap = dupmap;
646 }
647 } else {
648 SDL_free(*stream_chmap);
649 *stream_chmap = NULL;
650 }
651 }
652
653 SDL_UnlockMutex(stream->lock);
654 return result;
655}
656
657bool SDL_SetAudioStreamInputChannelMap(SDL_AudioStream *stream, const int *chmap, int channels)
658{
659 return SetAudioStreamChannelMap(stream, &stream->src_spec, &stream->src_chmap, chmap, channels, 1);
660}
661
662bool SDL_SetAudioStreamOutputChannelMap(SDL_AudioStream *stream, const int *chmap, int channels)
663{
664 return SetAudioStreamChannelMap(stream, &stream->dst_spec, &stream->dst_chmap, chmap, channels, 0);
665}
666
667int *SDL_GetAudioStreamInputChannelMap(SDL_AudioStream *stream, int *count)
668{
669 int *result = NULL;
670 int channels = 0;
671 if (stream) {
672 SDL_LockMutex(stream->lock);
673 channels = stream->src_spec.channels;
674 result = SDL_ChannelMapDup(stream->src_chmap, channels);
675 SDL_UnlockMutex(stream->lock);
676 }
677
678 if (count) {
679 *count = channels;
680 }
681
682 return result;
683}
684
685int *SDL_GetAudioStreamOutputChannelMap(SDL_AudioStream *stream, int *count)
686{
687 int *result = NULL;
688 int channels = 0;
689 if (stream) {
690 SDL_LockMutex(stream->lock);
691 channels = stream->dst_spec.channels;
692 result = SDL_ChannelMapDup(stream->dst_chmap, channels);
693 SDL_UnlockMutex(stream->lock);
694 }
695
696 if (count) {
697 *count = channels;
698 }
699
700 return result;
701}
702
703float SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream)
704{
705 if (!stream) {
706 SDL_InvalidParamError("stream");
707 return 0.0f;
708 }
709
710 SDL_LockMutex(stream->lock);
711 const float freq_ratio = stream->freq_ratio;
712 SDL_UnlockMutex(stream->lock);
713
714 return freq_ratio;
715}
716
717bool SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float freq_ratio)
718{
719 if (!stream) {
720 return SDL_InvalidParamError("stream");
721 }
722
723 // Picked mostly arbitrarily.
724 const float min_freq_ratio = 0.01f;
725 const float max_freq_ratio = 100.0f;
726
727 if (freq_ratio < min_freq_ratio) {
728 return SDL_SetError("Frequency ratio is too low");
729 } else if (freq_ratio > max_freq_ratio) {
730 return SDL_SetError("Frequency ratio is too high");
731 }
732
733 SDL_LockMutex(stream->lock);
734 stream->freq_ratio = freq_ratio;
735 SDL_UnlockMutex(stream->lock);
736
737 return true;
738}
739
740float SDL_GetAudioStreamGain(SDL_AudioStream *stream)
741{
742 if (!stream) {
743 SDL_InvalidParamError("stream");
744 return -1.0f;
745 }
746
747 SDL_LockMutex(stream->lock);
748 const float gain = stream->gain;
749 SDL_UnlockMutex(stream->lock);
750
751 return gain;
752}
753
754bool SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain)
755{
756 if (!stream) {
757 return SDL_InvalidParamError("stream");
758 } else if (gain < 0.0f) {
759 return SDL_InvalidParamError("gain");
760 }
761
762 SDL_LockMutex(stream->lock);
763 stream->gain = gain;
764 SDL_UnlockMutex(stream->lock);
765
766 return true;
767}
768
769static bool CheckAudioStreamIsFullySetup(SDL_AudioStream *stream)
770{
771 if (stream->src_spec.format == 0) {
772 return SDL_SetError("Stream has no source format");
773 } else if (stream->dst_spec.format == 0) {
774 return SDL_SetError("Stream has no destination format");
775 }
776
777 return true;
778}
779
780static bool PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void* userdata)
781{
782#if DEBUG_AUDIOSTREAM
783 SDL_Log("AUDIOSTREAM: wants to put %d bytes", len);
784#endif
785
786 SDL_LockMutex(stream->lock);
787
788 if (!CheckAudioStreamIsFullySetup(stream)) {
789 SDL_UnlockMutex(stream->lock);
790 return false;
791 }
792
793 if ((len % SDL_AUDIO_FRAMESIZE(stream->src_spec)) != 0) {
794 SDL_UnlockMutex(stream->lock);
795 return SDL_SetError("Can't add partial sample frames");
796 }
797
798 SDL_AudioTrack* track = NULL;
799
800 if (callback) {
801 track = SDL_CreateAudioTrack(stream->queue, &stream->src_spec, stream->src_chmap, (Uint8 *)buf, len, len, callback, userdata);
802
803 if (!track) {
804 SDL_UnlockMutex(stream->lock);
805 return false;
806 }
807 }
808
809 const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0;
810
811 bool result = true;
812
813 if (track) {
814 SDL_AddTrackToAudioQueue(stream->queue, track);
815 } else {
816 result = SDL_WriteToAudioQueue(stream->queue, &stream->src_spec, stream->src_chmap, (const Uint8 *)buf, len);
817 }
818
819 if (result) {
820 if (stream->put_callback) {
821 const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available;
822 stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail);
823 }
824 }
825
826 SDL_UnlockMutex(stream->lock);
827
828 return result;
829}
830
831static void SDLCALL FreeAllocatedAudioBuffer(void *userdata, const void *buf, int len)
832{
833 SDL_free((void*) buf);
834}
835
836bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)
837{
838 if (!stream) {
839 return SDL_InvalidParamError("stream");
840 } else if (!buf) {
841 return SDL_InvalidParamError("buf");
842 } else if (len < 0) {
843 return SDL_InvalidParamError("len");
844 } else if (len == 0) {
845 return true; // nothing to do.
846 }
847
848 // When copying in large amounts of data, try and do as much work as possible
849 // outside of the stream lock, otherwise the output device is likely to be starved.
850 const int large_input_thresh = 64 * 1024;
851
852 if (len >= large_input_thresh) {
853 void *data = SDL_malloc(len);
854
855 if (!data) {
856 return false;
857 }
858
859 SDL_memcpy(data, buf, len);
860 buf = data;
861
862 bool ret = PutAudioStreamBuffer(stream, buf, len, FreeAllocatedAudioBuffer, NULL);
863 if (!ret) {
864 SDL_free(data);
865 }
866 return ret;
867 }
868
869 return PutAudioStreamBuffer(stream, buf, len, NULL, NULL);
870}
871
872bool SDL_FlushAudioStream(SDL_AudioStream *stream)
873{
874 if (!stream) {
875 return SDL_InvalidParamError("stream");
876 }
877
878 SDL_LockMutex(stream->lock);
879 SDL_FlushAudioQueue(stream->queue);
880 SDL_UnlockMutex(stream->lock);
881
882 return true;
883}
884
885/* this does not save the previous contents of stream->work_buffer. It's a work buffer!!
886 The returned buffer is aligned/padded for use with SIMD instructions. */
887static Uint8 *EnsureAudioStreamWorkBufferSize(SDL_AudioStream *stream, size_t newlen)
888{
889 if (stream->work_buffer_allocation >= newlen) {
890 return stream->work_buffer;
891 }
892
893 Uint8 *ptr = (Uint8 *) SDL_aligned_alloc(SDL_GetSIMDAlignment(), newlen);
894 if (!ptr) {
895 return NULL; // previous work buffer is still valid!
896 }
897
898 SDL_aligned_free(stream->work_buffer);
899 stream->work_buffer = ptr;
900 stream->work_buffer_allocation = newlen;
901 return ptr;
902}
903
904static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter,
905 Sint64* inout_resample_offset, SDL_AudioSpec* out_spec, int **out_chmap, bool* out_flushed)
906{
907 SDL_AudioSpec spec;
908 bool flushed;
909 int *chmap;
910 size_t queued_bytes = SDL_NextAudioQueueIter(stream->queue, inout_iter, &spec, &chmap, &flushed);
911
912 if (out_spec) {
913 SDL_copyp(out_spec, &spec);
914 }
915
916 if (out_chmap) {
917 *out_chmap = chmap;
918 }
919
920 // There is infinite audio available, whether or not we are resampling
921 if (queued_bytes == SDL_SIZE_MAX) {
922 *inout_resample_offset = 0;
923
924 if (out_flushed) {
925 *out_flushed = false;
926 }
927
928 return SDL_MAX_SINT32;
929 }
930
931 Sint64 resample_offset = *inout_resample_offset;
932 Sint64 resample_rate = GetAudioStreamResampleRate(stream, spec.freq, resample_offset);
933 Sint64 output_frames = (Sint64)(queued_bytes / SDL_AUDIO_FRAMESIZE(spec));
934
935 if (resample_rate) {
936 // Resampling requires padding frames to the left and right of the current position.
937 // Past the end of the track, the right padding is filled with silence.
938 // But we only want to do that if the track is actually finished (flushed).
939 if (!flushed) {
940 output_frames -= SDL_GetResamplerPaddingFrames(resample_rate);
941 }
942
943 output_frames = SDL_GetResamplerOutputFrames(output_frames, resample_rate, &resample_offset);
944 }
945
946 if (flushed) {
947 resample_offset = 0;
948 }
949
950 *inout_resample_offset = resample_offset;
951
952 if (out_flushed) {
953 *out_flushed = flushed;
954 }
955
956 return output_frames;
957}
958
959static Sint64 GetAudioStreamAvailableFrames(SDL_AudioStream* stream, Sint64* out_resample_offset)
960{
961 void* iter = SDL_BeginAudioQueueIter(stream->queue);
962
963 Sint64 resample_offset = stream->resample_offset;
964 Sint64 output_frames = 0;
965
966 while (iter) {
967 output_frames += NextAudioStreamIter(stream, &iter, &resample_offset, NULL, NULL, NULL);
968
969 // Already got loads of frames. Just clamp it to something reasonable
970 if (output_frames >= SDL_MAX_SINT32) {
971 output_frames = SDL_MAX_SINT32;
972 break;
973 }
974 }
975
976 if (out_resample_offset) {
977 *out_resample_offset = resample_offset;
978 }
979
980 return output_frames;
981}
982
983static Sint64 GetAudioStreamHead(SDL_AudioStream* stream, SDL_AudioSpec* out_spec, int **out_chmap, bool* out_flushed)
984{
985 void* iter = SDL_BeginAudioQueueIter(stream->queue);
986
987 if (!iter) {
988 SDL_zerop(out_spec);
989 *out_flushed = false;
990 return 0;
991 }
992
993 Sint64 resample_offset = stream->resample_offset;
994 return NextAudioStreamIter(stream, &iter, &resample_offset, out_spec, out_chmap, out_flushed);
995}
996
997// You must hold stream->lock and validate your parameters before calling this!
998// Enough input data MUST be available!
999static bool GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int output_frames, float gain)
1000{
1001 const SDL_AudioSpec* src_spec = &stream->input_spec;
1002 const SDL_AudioSpec* dst_spec = &stream->dst_spec;
1003
1004 const SDL_AudioFormat src_format = src_spec->format;
1005 const int src_channels = src_spec->channels;
1006
1007 const SDL_AudioFormat dst_format = dst_spec->format;
1008 const int dst_channels = dst_spec->channels;
1009 const int *dst_map = stream->dst_chmap;
1010
1011 const int max_frame_size = CalculateMaxFrameSize(src_format, src_channels, dst_format, dst_channels);
1012 const Sint64 resample_rate = GetAudioStreamResampleRate(stream, src_spec->freq, stream->resample_offset);
1013
1014#if DEBUG_AUDIOSTREAM
1015 SDL_Log("AUDIOSTREAM: asking for %d frames.", output_frames);
1016#endif
1017
1018 SDL_assert(output_frames > 0);
1019
1020 // Not resampling? It's an easy conversion (and maybe not even that!)
1021 if (resample_rate == 0) {
1022 Uint8* work_buffer = NULL;
1023
1024 // Ensure we have enough scratch space for any conversions
1025 if ((src_format != dst_format) || (src_channels != dst_channels) || (gain != 1.0f)) {
1026 work_buffer = EnsureAudioStreamWorkBufferSize(stream, output_frames * max_frame_size);
1027
1028 if (!work_buffer) {
1029 return false;
1030 }
1031 }
1032
1033 if (SDL_ReadFromAudioQueue(stream->queue, (Uint8 *)buf, dst_format, dst_channels, dst_map, 0, output_frames, 0, work_buffer, gain) != buf) {
1034 return SDL_SetError("Not enough data in queue");
1035 }
1036
1037 return true;
1038 }
1039
1040 // Time to do some resampling!
1041 // Calculate the number of input frames necessary for this request.
1042 // Because resampling happens "between" frames, The same number of output_frames
1043 // can require a different number of input_frames, depending on the resample_offset.
1044 // In fact, input_frames can sometimes even be zero when upsampling.
1045 const int input_frames = (int) SDL_GetResamplerInputFrames(output_frames, resample_rate, stream->resample_offset);
1046
1047 const int padding_frames = SDL_GetResamplerPaddingFrames(resample_rate);
1048
1049 const SDL_AudioFormat resample_format = SDL_AUDIO_F32;
1050
1051 // If increasing channels, do it after resampling, since we'd just
1052 // do more work to resample duplicate channels. If we're decreasing, do
1053 // it first so we resample the interpolated data instead of interpolating
1054 // the resampled data.
1055 const int resample_channels = SDL_min(src_channels, dst_channels);
1056
1057 // The size of the frame used when resampling
1058 const int resample_frame_size = SDL_AUDIO_BYTESIZE(resample_format) * resample_channels;
1059
1060 // The main portion of the work_buffer can be used to store 3 things:
1061 // src_sample_frame_size * (left_padding+input_buffer+right_padding)
1062 // resample_frame_size * (left_padding+input_buffer+right_padding)
1063 // dst_sample_frame_size * output_frames
1064 //
1065 // ResampleAudio also requires an additional buffer if it can't write straight to the output:
1066 // resample_frame_size * output_frames
1067 //
1068 // Note, ConvertAudio requires (num_frames * max_sample_frame_size) of scratch space
1069 const int work_buffer_frames = input_frames + (padding_frames * 2);
1070 int work_buffer_capacity = work_buffer_frames * max_frame_size;
1071 int resample_buffer_offset = -1;
1072
1073 // Check if we can resample directly into the output buffer.
1074 // Note, this is just to avoid extra copies.
1075 // Some other formats may fit directly into the output buffer, but i'd rather process data in a SIMD-aligned buffer.
1076 if ((dst_format != resample_format) || (dst_channels != resample_channels)) {
1077 // Allocate space for converting the resampled output to the destination format
1078 int resample_convert_bytes = output_frames * max_frame_size;
1079 work_buffer_capacity = SDL_max(work_buffer_capacity, resample_convert_bytes);
1080
1081 // SIMD-align the buffer
1082 int simd_alignment = (int) SDL_GetSIMDAlignment();
1083 work_buffer_capacity += simd_alignment - 1;
1084 work_buffer_capacity -= work_buffer_capacity % simd_alignment;
1085
1086 // Allocate space for the resampled output
1087 int resample_bytes = output_frames * resample_frame_size;
1088 resample_buffer_offset = work_buffer_capacity;
1089 work_buffer_capacity += resample_bytes;
1090 }
1091
1092 Uint8* work_buffer = EnsureAudioStreamWorkBufferSize(stream, work_buffer_capacity);
1093
1094 if (!work_buffer) {
1095 return false;
1096 }
1097
1098 // adjust gain either before resampling or after, depending on which point has less
1099 // samples to process.
1100 const float preresample_gain = (input_frames > output_frames) ? 1.0f : gain;
1101 const float postresample_gain = (input_frames > output_frames) ? gain : 1.0f;
1102
1103 // (dst channel map is NULL because we'll do the final swizzle on ConvertAudio after resample.)
1104 const Uint8* input_buffer = SDL_ReadFromAudioQueue(stream->queue,
1105 NULL, resample_format, resample_channels, NULL,
1106 padding_frames, input_frames, padding_frames, work_buffer, preresample_gain);
1107
1108 if (!input_buffer) {
1109 return SDL_SetError("Not enough data in queue (resample)");
1110 }
1111
1112 input_buffer += padding_frames * resample_frame_size;
1113
1114 // Decide where the resampled output goes
1115 void* resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf;
1116
1117 SDL_ResampleAudio(resample_channels,
1118 (const float *) input_buffer, input_frames,
1119 (float*) resample_buffer, output_frames,
1120 resample_rate, &stream->resample_offset);
1121
1122 // Convert to the final format, if necessary (src channel map is NULL because SDL_ReadFromAudioQueue already handled this).
1123 ConvertAudio(output_frames, resample_buffer, resample_format, resample_channels, NULL, buf, dst_format, dst_channels, dst_map, work_buffer, postresample_gain);
1124
1125 return true;
1126}
1127
1128// get converted/resampled data from the stream
1129int SDL_GetAudioStreamDataAdjustGain(SDL_AudioStream *stream, void *voidbuf, int len, float extra_gain)
1130{
1131 Uint8 *buf = (Uint8 *) voidbuf;
1132
1133#if DEBUG_AUDIOSTREAM
1134 SDL_Log("AUDIOSTREAM: want to get %d converted bytes", len);
1135#endif
1136
1137 if (!stream) {
1138 SDL_InvalidParamError("stream");
1139 return -1;
1140 } else if (!buf) {
1141 SDL_InvalidParamError("buf");
1142 return -1;
1143 } else if (len < 0) {
1144 SDL_InvalidParamError("len");
1145 return -1;
1146 } else if (len == 0) {
1147 return 0; // nothing to do.
1148 }
1149
1150 SDL_LockMutex(stream->lock);
1151
1152 if (!CheckAudioStreamIsFullySetup(stream)) {
1153 SDL_UnlockMutex(stream->lock);
1154 return -1;
1155 }
1156
1157 const float gain = stream->gain * extra_gain;
1158 const int dst_frame_size = SDL_AUDIO_FRAMESIZE(stream->dst_spec);
1159
1160 len -= len % dst_frame_size; // chop off any fractional sample frame.
1161
1162 // give the callback a chance to fill in more stream data if it wants.
1163 if (stream->get_callback) {
1164 Sint64 total_request = len / dst_frame_size; // start with sample frames desired
1165 Sint64 additional_request = total_request;
1166
1167 Sint64 resample_offset = 0;
1168 Sint64 available_frames = GetAudioStreamAvailableFrames(stream, &resample_offset);
1169
1170 additional_request -= SDL_min(additional_request, available_frames);
1171
1172 Sint64 resample_rate = GetAudioStreamResampleRate(stream, stream->src_spec.freq, resample_offset);
1173
1174 if (resample_rate) {
1175 total_request = SDL_GetResamplerInputFrames(total_request, resample_rate, resample_offset);
1176 additional_request = SDL_GetResamplerInputFrames(additional_request, resample_rate, resample_offset);
1177 }
1178
1179 total_request *= SDL_AUDIO_FRAMESIZE(stream->src_spec); // convert sample frames to bytes.
1180 additional_request *= SDL_AUDIO_FRAMESIZE(stream->src_spec); // convert sample frames to bytes.
1181 stream->get_callback(stream->get_callback_userdata, stream, (int) SDL_min(additional_request, SDL_INT_MAX), (int) SDL_min(total_request, SDL_INT_MAX));
1182 }
1183
1184 // Process the data in chunks to avoid allocating too much memory (and potential integer overflows)
1185 const int chunk_size = 4096;
1186
1187 int total = 0;
1188
1189 while (total < len) {
1190 // Audio is processed a track at a time.
1191 SDL_AudioSpec input_spec;
1192 int *input_chmap;
1193 bool flushed;
1194 const Sint64 available_frames = GetAudioStreamHead(stream, &input_spec, &input_chmap, &flushed);
1195
1196 if (available_frames == 0) {
1197 if (flushed) {
1198 SDL_PopAudioQueueHead(stream->queue);
1199 SDL_zero(stream->input_spec);
1200 stream->resample_offset = 0;
1201 stream->input_chmap = NULL;
1202 continue;
1203 }
1204 // There are no frames available, but the track hasn't been flushed, so more might be added later.
1205 break;
1206 }
1207
1208 if (!UpdateAudioStreamInputSpec(stream, &input_spec, input_chmap)) {
1209 total = total ? total : -1;
1210 break;
1211 }
1212
1213 // Clamp the output length to the maximum currently available.
1214 // GetAudioStreamDataInternal requires enough input data is available.
1215 int output_frames = (len - total) / dst_frame_size;
1216 output_frames = SDL_min(output_frames, chunk_size);
1217 output_frames = (int) SDL_min(output_frames, available_frames);
1218
1219 if (!GetAudioStreamDataInternal(stream, &buf[total], output_frames, gain)) {
1220 total = total ? total : -1;
1221 break;
1222 }
1223
1224 total += output_frames * dst_frame_size;
1225 }
1226
1227 SDL_UnlockMutex(stream->lock);
1228
1229#if DEBUG_AUDIOSTREAM
1230 SDL_Log("AUDIOSTREAM: Final result was %d", total);
1231#endif
1232
1233 return total;
1234}
1235
1236int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len)
1237{
1238 return SDL_GetAudioStreamDataAdjustGain(stream, voidbuf, len, 1.0f);
1239}
1240
1241// number of converted/resampled bytes available for output
1242int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream)
1243{
1244 if (!stream) {
1245 SDL_InvalidParamError("stream");
1246 return -1;
1247 }
1248
1249 SDL_LockMutex(stream->lock);
1250
1251 if (!CheckAudioStreamIsFullySetup(stream)) {
1252 SDL_UnlockMutex(stream->lock);
1253 return 0;
1254 }
1255
1256 Sint64 count = GetAudioStreamAvailableFrames(stream, NULL);
1257
1258 // convert from sample frames to bytes in destination format.
1259 count *= SDL_AUDIO_FRAMESIZE(stream->dst_spec);
1260
1261 SDL_UnlockMutex(stream->lock);
1262
1263 // if this overflows an int, just clamp it to a maximum.
1264 return (int) SDL_min(count, SDL_INT_MAX);
1265}
1266
1267// number of sample frames that are currently queued as input.
1268int SDL_GetAudioStreamQueued(SDL_AudioStream *stream)
1269{
1270 if (!stream) {
1271 SDL_InvalidParamError("stream");
1272 return -1;
1273 }
1274
1275 SDL_LockMutex(stream->lock);
1276
1277 size_t total = SDL_GetAudioQueueQueued(stream->queue);
1278
1279 SDL_UnlockMutex(stream->lock);
1280
1281 // if this overflows an int, just clamp it to a maximum.
1282 return (int) SDL_min(total, SDL_INT_MAX);
1283}
1284
1285bool SDL_ClearAudioStream(SDL_AudioStream *stream)
1286{
1287 if (!stream) {
1288 return SDL_InvalidParamError("stream");
1289 }
1290
1291 SDL_LockMutex(stream->lock);
1292
1293 SDL_ClearAudioQueue(stream->queue);
1294 SDL_zero(stream->input_spec);
1295 stream->input_chmap = NULL;
1296 stream->resample_offset = 0;
1297
1298 SDL_UnlockMutex(stream->lock);
1299 return true;
1300}
1301
1302void SDL_DestroyAudioStream(SDL_AudioStream *stream)
1303{
1304 if (!stream) {
1305 return;
1306 }
1307
1308 SDL_DestroyProperties(stream->props);
1309
1310 OnAudioStreamDestroy(stream);
1311
1312 const bool simplified = stream->simplified;
1313 if (simplified) {
1314 if (stream->bound_device) {
1315 SDL_assert(stream->bound_device->simplified);
1316 SDL_CloseAudioDevice(stream->bound_device->instance_id); // this will unbind the stream.
1317 }
1318 } else {
1319 SDL_UnbindAudioStream(stream);
1320 }
1321
1322 SDL_aligned_free(stream->work_buffer);
1323 SDL_DestroyAudioQueue(stream->queue);
1324 SDL_DestroyMutex(stream->lock);
1325
1326 SDL_free(stream);
1327}
1328
1329static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int len)
1330{
1331 // We don't own the buffer, but know it will outlive the stream
1332}
1333
1334bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len)
1335{
1336 if (dst_data) {
1337 *dst_data = NULL;
1338 }
1339
1340 if (dst_len) {
1341 *dst_len = 0;
1342 }
1343
1344 if (!src_data) {
1345 return SDL_InvalidParamError("src_data");
1346 } else if (src_len < 0) {
1347 return SDL_InvalidParamError("src_len");
1348 } else if (!dst_data) {
1349 return SDL_InvalidParamError("dst_data");
1350 } else if (!dst_len) {
1351 return SDL_InvalidParamError("dst_len");
1352 }
1353
1354 bool result = false;
1355 Uint8 *dst = NULL;
1356 int dstlen = 0;
1357
1358 SDL_AudioStream *stream = SDL_CreateAudioStream(src_spec, dst_spec);
1359 if (stream) {
1360 if (PutAudioStreamBuffer(stream, src_data, src_len, DontFreeThisAudioBuffer, NULL) &&
1361 SDL_FlushAudioStream(stream)) {
1362 dstlen = SDL_GetAudioStreamAvailable(stream);
1363 if (dstlen >= 0) {
1364 dst = (Uint8 *)SDL_malloc(dstlen);
1365 if (dst) {
1366 result = (SDL_GetAudioStreamData(stream, dst, dstlen) == dstlen);
1367 }
1368 }
1369 }
1370 }
1371
1372 if (result) {
1373 *dst_data = dst;
1374 *dst_len = dstlen;
1375 } else {
1376 SDL_free(dst);
1377 }
1378
1379 SDL_DestroyAudioStream(stream);
1380 return result;
1381}
1382