summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/audio/coreaudio
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/audio/coreaudio')
-rw-r--r--contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h68
-rw-r--r--contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m1040
2 files changed, 1108 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h b/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h
new file mode 100644
index 0000000..f117c09
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h
@@ -0,0 +1,68 @@
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#ifndef SDL_coreaudio_h_
24#define SDL_coreaudio_h_
25
26#include "../SDL_sysaudio.h"
27
28#ifndef SDL_PLATFORM_IOS
29#define MACOSX_COREAUDIO
30#endif
31
32#ifdef MACOSX_COREAUDIO
33#include <CoreAudio/CoreAudio.h>
34#else
35#import <AVFoundation/AVFoundation.h>
36#import <UIKit/UIApplication.h>
37#endif
38
39#include <AudioToolbox/AudioToolbox.h>
40#include <AudioUnit/AudioUnit.h>
41
42// Things named "Master" were renamed to "Main" in macOS 12.0's SDK.
43#ifdef MACOSX_COREAUDIO
44#include <AvailabilityMacros.h>
45#ifndef MAC_OS_VERSION_12_0
46#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster
47#endif
48#endif
49
50struct SDL_PrivateAudioData
51{
52 SDL_Thread *thread;
53 AudioQueueRef audioQueue;
54 int numAudioBuffers;
55 AudioQueueBufferRef *audioBuffer;
56 AudioQueueBufferRef current_buffer;
57 AudioStreamBasicDescription strdesc;
58 SDL_Semaphore *ready_semaphore;
59 char *thread_error;
60#ifdef MACOSX_COREAUDIO
61 AudioDeviceID deviceID;
62#else
63 bool interrupted;
64 CFTypeRef interruption_listener;
65#endif
66};
67
68#endif // SDL_coreaudio_h_
diff --git a/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m b/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m
new file mode 100644
index 0000000..57b19c7
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m
@@ -0,0 +1,1040 @@
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#ifdef SDL_AUDIO_DRIVER_COREAUDIO
24
25#include "../SDL_sysaudio.h"
26#include "SDL_coreaudio.h"
27#include "../../thread/SDL_systhread.h"
28
29#define DEBUG_COREAUDIO 0
30
31#if DEBUG_COREAUDIO
32 #define CHECK_RESULT(msg) \
33 if (result != noErr) { \
34 SDL_Log("COREAUDIO: Got error %d from '%s'!", (int)result, msg); \
35 return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \
36 }
37#else
38 #define CHECK_RESULT(msg) \
39 if (result != noErr) { \
40 return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \
41 }
42#endif
43
44#ifdef MACOSX_COREAUDIO
45// Apparently AudioDeviceID values might not be unique, so we wrap it in an SDL_malloc()'d pointer
46// to make it so. Use FindCoreAudioDeviceByHandle to deal with this redirection, if you need to
47// map from an AudioDeviceID to a SDL handle.
48typedef struct SDLCoreAudioHandle
49{
50 AudioDeviceID devid;
51 bool recording;
52} SDLCoreAudioHandle;
53
54static bool TestCoreAudioDeviceHandleCallback(SDL_AudioDevice *device, void *handle)
55{
56 const SDLCoreAudioHandle *a = (const SDLCoreAudioHandle *) device->handle;
57 const SDLCoreAudioHandle *b = (const SDLCoreAudioHandle *) handle;
58 return (a->devid == b->devid) && (!!a->recording == !!b->recording);
59}
60
61static SDL_AudioDevice *FindCoreAudioDeviceByHandle(const AudioDeviceID devid, const bool recording)
62{
63 SDLCoreAudioHandle handle = { devid, recording };
64 return SDL_FindPhysicalAudioDeviceByCallback(TestCoreAudioDeviceHandleCallback, &handle);
65}
66
67static const AudioObjectPropertyAddress devlist_address = {
68 kAudioHardwarePropertyDevices,
69 kAudioObjectPropertyScopeGlobal,
70 kAudioObjectPropertyElementMain
71};
72
73static const AudioObjectPropertyAddress default_playback_device_address = {
74 kAudioHardwarePropertyDefaultOutputDevice,
75 kAudioObjectPropertyScopeGlobal,
76 kAudioObjectPropertyElementMain
77};
78
79static const AudioObjectPropertyAddress default_recording_device_address = {
80 kAudioHardwarePropertyDefaultInputDevice,
81 kAudioObjectPropertyScopeGlobal,
82 kAudioObjectPropertyElementMain
83};
84
85static const AudioObjectPropertyAddress alive_address = {
86 kAudioDevicePropertyDeviceIsAlive,
87 kAudioObjectPropertyScopeGlobal,
88 kAudioObjectPropertyElementMain
89};
90
91
92static OSStatus DeviceAliveNotification(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
93{
94 SDL_AudioDevice *device = (SDL_AudioDevice *)data;
95 SDL_assert(((const SDLCoreAudioHandle *) device->handle)->devid == devid);
96
97 UInt32 alive = 1;
98 UInt32 size = sizeof(alive);
99 const OSStatus error = AudioObjectGetPropertyData(devid, addrs, 0, NULL, &size, &alive);
100
101 bool dead = false;
102 if (error == kAudioHardwareBadDeviceError) {
103 dead = true; // device was unplugged.
104 } else if ((error == kAudioHardwareNoError) && (!alive)) {
105 dead = true; // device died in some other way.
106 }
107
108 if (dead) {
109 #if DEBUG_COREAUDIO
110 SDL_Log("COREAUDIO: device '%s' is lost!", device->name);
111 #endif
112 SDL_AudioDeviceDisconnected(device);
113 }
114
115 return noErr;
116}
117
118static void COREAUDIO_FreeDeviceHandle(SDL_AudioDevice *device)
119{
120 SDLCoreAudioHandle *handle = (SDLCoreAudioHandle *) device->handle;
121 AudioObjectRemovePropertyListener(handle->devid, &alive_address, DeviceAliveNotification, device);
122 SDL_free(handle);
123}
124
125// This only _adds_ new devices. Removal is handled by devices triggering kAudioDevicePropertyDeviceIsAlive property changes.
126static void RefreshPhysicalDevices(void)
127{
128 UInt32 size = 0;
129 AudioDeviceID *devs = NULL;
130 bool isstack;
131
132 if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &devlist_address, 0, NULL, &size) != kAudioHardwareNoError) {
133 return;
134 } else if ((devs = (AudioDeviceID *) SDL_small_alloc(Uint8, size, &isstack)) == NULL) {
135 return;
136 } else if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &devlist_address, 0, NULL, &size, devs) != kAudioHardwareNoError) {
137 SDL_small_free(devs, isstack);
138 return;
139 }
140
141 const UInt32 total_devices = (UInt32) (size / sizeof(AudioDeviceID));
142 for (UInt32 i = 0; i < total_devices; i++) {
143 if (FindCoreAudioDeviceByHandle(devs[i], true) || FindCoreAudioDeviceByHandle(devs[i], false)) {
144 devs[i] = 0; // The system and SDL both agree it's already here, don't check it again.
145 }
146 }
147
148 // any non-zero items remaining in `devs` are new devices to be added.
149 for (int recording = 0; recording < 2; recording++) {
150 const AudioObjectPropertyAddress addr = {
151 kAudioDevicePropertyStreamConfiguration,
152 recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
153 kAudioObjectPropertyElementMain
154 };
155 const AudioObjectPropertyAddress nameaddr = {
156 kAudioObjectPropertyName,
157 recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
158 kAudioObjectPropertyElementMain
159 };
160 const AudioObjectPropertyAddress freqaddr = {
161 kAudioDevicePropertyNominalSampleRate,
162 recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
163 kAudioObjectPropertyElementMain
164 };
165
166 for (UInt32 i = 0; i < total_devices; i++) {
167 const AudioDeviceID dev = devs[i];
168 if (!dev) {
169 continue; // already added.
170 }
171
172 AudioBufferList *buflist = NULL;
173 double sampleRate = 0;
174
175 if (AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size) != noErr) {
176 continue;
177 } else if ((buflist = (AudioBufferList *)SDL_malloc(size)) == NULL) {
178 continue;
179 }
180
181 OSStatus result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, buflist);
182
183 SDL_AudioSpec spec;
184 SDL_zero(spec);
185 if (result == noErr) {
186 for (Uint32 j = 0; j < buflist->mNumberBuffers; j++) {
187 spec.channels += buflist->mBuffers[j].mNumberChannels;
188 }
189 }
190
191 SDL_free(buflist);
192
193 if (spec.channels == 0) {
194 continue;
195 }
196
197 size = sizeof(sampleRate);
198 if (AudioObjectGetPropertyData(dev, &freqaddr, 0, NULL, &size, &sampleRate) == noErr) {
199 spec.freq = (int)sampleRate;
200 }
201
202 CFStringRef cfstr = NULL;
203 size = sizeof(CFStringRef);
204 if (AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr) != kAudioHardwareNoError) {
205 continue;
206 }
207
208 CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), kCFStringEncodingUTF8);
209 char *name = (char *)SDL_malloc(len + 1);
210 bool usable = ((name != NULL) && (CFStringGetCString(cfstr, name, len + 1, kCFStringEncodingUTF8)));
211
212 CFRelease(cfstr);
213
214 if (usable) {
215 // Some devices have whitespace at the end...trim it.
216 len = (CFIndex) SDL_strlen(name);
217 while ((len > 0) && (name[len - 1] == ' ')) {
218 len--;
219 }
220 usable = (len > 0);
221 }
222
223 if (usable) {
224 name[len] = '\0';
225
226 #if DEBUG_COREAUDIO
227 SDL_Log("COREAUDIO: Found %s device #%d: '%s' (devid %d)", ((recording) ? "recording" : "playback"), (int)i, name, (int)dev);
228 #endif
229 SDLCoreAudioHandle *newhandle = (SDLCoreAudioHandle *) SDL_calloc(1, sizeof (*newhandle));
230 if (newhandle) {
231 newhandle->devid = dev;
232 newhandle->recording = recording ? true : false;
233 SDL_AudioDevice *device = SDL_AddAudioDevice(newhandle->recording, name, &spec, newhandle);
234 if (device) {
235 AudioObjectAddPropertyListener(dev, &alive_address, DeviceAliveNotification, device);
236 } else {
237 SDL_free(newhandle);
238 }
239 }
240 }
241 SDL_free(name); // SDL_AddAudioDevice() would have copied the string.
242 }
243 }
244
245 SDL_small_free(devs, isstack);
246}
247
248// this is called when the system's list of available audio devices changes.
249static OSStatus DeviceListChangedNotification(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
250{
251 RefreshPhysicalDevices();
252 return noErr;
253}
254
255static OSStatus DefaultAudioDeviceChangedNotification(const bool recording, AudioObjectID inObjectID, const AudioObjectPropertyAddress *addr)
256{
257 AudioDeviceID devid;
258 UInt32 size = sizeof(devid);
259 if (AudioObjectGetPropertyData(inObjectID, addr, 0, NULL, &size, &devid) == noErr) {
260 SDL_DefaultAudioDeviceChanged(FindCoreAudioDeviceByHandle(devid, recording));
261 }
262 return noErr;
263}
264
265static OSStatus DefaultPlaybackDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData)
266{
267 #if DEBUG_COREAUDIO
268 SDL_Log("COREAUDIO: default playback device changed!");
269 #endif
270 SDL_assert(inNumberAddresses == 1);
271 return DefaultAudioDeviceChangedNotification(false, inObjectID, inAddresses);
272}
273
274static OSStatus DefaultRecordingDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData)
275{
276 #if DEBUG_COREAUDIO
277 SDL_Log("COREAUDIO: default recording device changed!");
278 #endif
279 SDL_assert(inNumberAddresses == 1);
280 return DefaultAudioDeviceChangedNotification(true, inObjectID, inAddresses);
281}
282
283static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
284{
285 RefreshPhysicalDevices();
286
287 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL);
288
289 // Get the Device ID
290 UInt32 size;
291 AudioDeviceID devid;
292
293 size = sizeof(AudioDeviceID);
294 if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_playback_device_address, 0, NULL, &size, &devid) == noErr) {
295 SDL_AudioDevice *device = FindCoreAudioDeviceByHandle(devid, false);
296 if (device) {
297 *default_playback = device;
298 }
299 }
300 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_playback_device_address, DefaultPlaybackDeviceChangedNotification, NULL);
301
302 size = sizeof(AudioDeviceID);
303 if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_recording_device_address, 0, NULL, &size, &devid) == noErr) {
304 SDL_AudioDevice *device = FindCoreAudioDeviceByHandle(devid, true);
305 if (device) {
306 *default_recording = device;
307 }
308 }
309 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_recording_device_address, DefaultRecordingDeviceChangedNotification, NULL);
310}
311
312#else // iOS-specific section follows.
313
314static bool session_active = false;
315
316static bool PauseOneAudioDevice(SDL_AudioDevice *device, void *userdata)
317{
318 if (device->hidden && device->hidden->audioQueue && !device->hidden->interrupted) {
319 AudioQueuePause(device->hidden->audioQueue);
320 }
321 return false; // keep enumerating devices until we've paused them all.
322}
323
324static void PauseAudioDevices(void)
325{
326 (void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneAudioDevice, NULL);
327}
328
329static bool ResumeOneAudioDevice(SDL_AudioDevice *device, void *userdata)
330{
331 if (device->hidden && device->hidden->audioQueue && !device->hidden->interrupted) {
332 AudioQueueStart(device->hidden->audioQueue, NULL);
333 }
334 return false; // keep enumerating devices until we've resumed them all.
335}
336
337static void ResumeAudioDevices(void)
338{
339 (void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneAudioDevice, NULL);
340}
341
342static void InterruptionBegin(SDL_AudioDevice *device)
343{
344 if (device != NULL && device->hidden->audioQueue != NULL) {
345 device->hidden->interrupted = true;
346 AudioQueuePause(device->hidden->audioQueue);
347 }
348}
349
350static void InterruptionEnd(SDL_AudioDevice *device)
351{
352 if (device != NULL && device->hidden != NULL && device->hidden->audioQueue != NULL && device->hidden->interrupted && AudioQueueStart(device->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) {
353 device->hidden->interrupted = false;
354 }
355}
356
357@interface SDLInterruptionListener : NSObject
358
359@property(nonatomic, assign) SDL_AudioDevice *device;
360
361@end
362
363@implementation SDLInterruptionListener
364
365- (void)audioSessionInterruption:(NSNotification *)note
366{
367 @synchronized(self) {
368 NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];
369 if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) {
370 InterruptionBegin(self.device);
371 } else {
372 InterruptionEnd(self.device);
373 }
374 }
375}
376
377- (void)applicationBecameActive:(NSNotification *)note
378{
379 @synchronized(self) {
380 InterruptionEnd(self.device);
381 }
382}
383
384@end
385
386typedef struct
387{
388 int playback;
389 int recording;
390} CountOpenAudioDevicesData;
391
392static bool CountOpenAudioDevices(SDL_AudioDevice *device, void *userdata)
393{
394 CountOpenAudioDevicesData *data = (CountOpenAudioDevicesData *) userdata;
395 if (device->hidden != NULL) { // assume it's open if hidden != NULL
396 if (device->recording) {
397 data->recording++;
398 } else {
399 data->playback++;
400 }
401 }
402 return false; // keep enumerating until all devices have been checked.
403}
404
405static bool UpdateAudioSession(SDL_AudioDevice *device, bool open, bool allow_playandrecord)
406{
407 @autoreleasepool {
408 AVAudioSession *session = [AVAudioSession sharedInstance];
409 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
410
411 NSString *category = AVAudioSessionCategoryPlayback;
412 NSString *mode = AVAudioSessionModeDefault;
413 NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers;
414 NSError *err = nil;
415 const char *hint;
416
417 CountOpenAudioDevicesData data;
418 SDL_zero(data);
419 (void) SDL_FindPhysicalAudioDeviceByCallback(CountOpenAudioDevices, &data);
420
421 hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY);
422 if (hint) {
423 if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) {
424 category = AVAudioSessionCategoryAmbient;
425 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) {
426 category = AVAudioSessionCategorySoloAmbient;
427 options &= ~AVAudioSessionCategoryOptionMixWithOthers;
428 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 ||
429 SDL_strcasecmp(hint, "playback") == 0) {
430 category = AVAudioSessionCategoryPlayback;
431 options &= ~AVAudioSessionCategoryOptionMixWithOthers;
432 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayAndRecord") == 0 ||
433 SDL_strcasecmp(hint, "playandrecord") == 0) {
434 if (allow_playandrecord) {
435 category = AVAudioSessionCategoryPlayAndRecord;
436 }
437 }
438 } else if (data.playback && data.recording) {
439 if (allow_playandrecord) {
440 category = AVAudioSessionCategoryPlayAndRecord;
441 } else {
442 // We already failed play and record with AVAudioSessionErrorCodeResourceNotAvailable
443 return false;
444 }
445 } else if (data.recording) {
446 category = AVAudioSessionCategoryRecord;
447 }
448
449 #ifndef SDL_PLATFORM_TVOS
450 if (category == AVAudioSessionCategoryPlayAndRecord) {
451 options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
452 }
453 #endif
454 if (category == AVAudioSessionCategoryRecord ||
455 category == AVAudioSessionCategoryPlayAndRecord) {
456 /* AVAudioSessionCategoryOptionAllowBluetooth isn't available in the SDK for
457 Apple TV but is still needed in order to output to Bluetooth devices.
458 */
459 options |= 0x4; // AVAudioSessionCategoryOptionAllowBluetooth;
460 }
461 if (category == AVAudioSessionCategoryPlayAndRecord) {
462 options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP |
463 AVAudioSessionCategoryOptionAllowAirPlay;
464 }
465 if (category == AVAudioSessionCategoryPlayback ||
466 category == AVAudioSessionCategoryPlayAndRecord) {
467 options |= AVAudioSessionCategoryOptionDuckOthers;
468 }
469
470 if (![session.category isEqualToString:category] || session.categoryOptions != options) {
471 // Stop the current session so we don't interrupt other application audio
472 PauseAudioDevices();
473 [session setActive:NO error:nil];
474 session_active = false;
475
476 if (![session setCategory:category mode:mode options:options error:&err]) {
477 NSString *desc = err.description;
478 SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String);
479 return false;
480 }
481 }
482
483 if ((data.playback || data.recording) && !session_active) {
484 if (![session setActive:YES error:&err]) {
485 if ([err code] == AVAudioSessionErrorCodeResourceNotAvailable &&
486 category == AVAudioSessionCategoryPlayAndRecord) {
487 if (UpdateAudioSession(device, open, false)) {
488 return true;
489 } else {
490 return SDL_SetError("Could not activate Audio Session: Resource not available");
491 }
492 }
493
494 NSString *desc = err.description;
495 return SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String);
496 }
497 session_active = true;
498 ResumeAudioDevices();
499 } else if (!data.playback && !data.recording && session_active) {
500 PauseAudioDevices();
501 [session setActive:NO error:nil];
502 session_active = false;
503 }
504
505 if (open) {
506 SDLInterruptionListener *listener = [SDLInterruptionListener new];
507 listener.device = device;
508
509 [center addObserver:listener
510 selector:@selector(audioSessionInterruption:)
511 name:AVAudioSessionInterruptionNotification
512 object:session];
513
514 /* An interruption end notification is not guaranteed to be sent if
515 we were previously interrupted... resuming if needed when the app
516 becomes active seems to be the way to go. */
517 // Note: object: below needs to be nil, as otherwise it filters by the object, and session doesn't send foreground / active notifications.
518 [center addObserver:listener
519 selector:@selector(applicationBecameActive:)
520 name:UIApplicationDidBecomeActiveNotification
521 object:nil];
522
523 [center addObserver:listener
524 selector:@selector(applicationBecameActive:)
525 name:UIApplicationWillEnterForegroundNotification
526 object:nil];
527
528 device->hidden->interruption_listener = CFBridgingRetain(listener);
529 } else {
530 SDLInterruptionListener *listener = nil;
531 listener = (SDLInterruptionListener *)CFBridgingRelease(device->hidden->interruption_listener);
532 [center removeObserver:listener];
533 @synchronized(listener) {
534 listener.device = NULL;
535 }
536 }
537 }
538
539 return true;
540}
541#endif
542
543
544static bool COREAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
545{
546 AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
547 SDL_assert(current_buffer != NULL); // should have been called from PlaybackBufferReadyCallback
548 SDL_assert(buffer == (Uint8 *) current_buffer->mAudioData);
549 current_buffer->mAudioDataByteSize = current_buffer->mAudioDataBytesCapacity;
550 device->hidden->current_buffer = NULL;
551 AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL);
552 return true;
553}
554
555static Uint8 *COREAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
556{
557 AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
558 SDL_assert(current_buffer != NULL); // should have been called from PlaybackBufferReadyCallback
559 SDL_assert(current_buffer->mAudioData != NULL);
560 *buffer_size = (int) current_buffer->mAudioDataBytesCapacity;
561 return (Uint8 *) current_buffer->mAudioData;
562}
563
564static void PlaybackBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
565{
566 SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData;
567 SDL_assert(inBuffer != NULL); // ...right?
568 SDL_assert(device->hidden->current_buffer == NULL); // shouldn't have anything pending
569 device->hidden->current_buffer = inBuffer;
570 const bool okay = SDL_PlaybackAudioThreadIterate(device);
571 SDL_assert((device->hidden->current_buffer == NULL) || !okay); // PlayDevice should have enqueued and cleaned it out, unless we failed or shutdown.
572
573 // buffer is unexpectedly here? We're probably dying, but try to requeue this buffer with silence.
574 if (device->hidden->current_buffer) {
575 AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
576 device->hidden->current_buffer = NULL;
577 SDL_memset(current_buffer->mAudioData, device->silence_value, (size_t) current_buffer->mAudioDataBytesCapacity);
578 AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL);
579 }
580}
581
582static int COREAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
583{
584 AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
585 SDL_assert(current_buffer != NULL); // should have been called from RecordingBufferReadyCallback
586 SDL_assert(current_buffer->mAudioData != NULL);
587 SDL_assert(buflen >= (int) current_buffer->mAudioDataByteSize); // `cpy` makes sure this won't overflow a buffer, but we _will_ drop samples if this assertion fails!
588 const int cpy = SDL_min(buflen, (int) current_buffer->mAudioDataByteSize);
589 SDL_memcpy(buffer, current_buffer->mAudioData, cpy);
590 device->hidden->current_buffer = NULL;
591 AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); // requeue for capturing more data later.
592 return cpy;
593}
594
595static void COREAUDIO_FlushRecording(SDL_AudioDevice *device)
596{
597 AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
598 if (current_buffer != NULL) { // also gets called at shutdown, when no buffer is available.
599 // just requeue the current buffer without reading from it, so it can be refilled with new data later.
600 device->hidden->current_buffer = NULL;
601 AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL);
602 }
603}
604
605static void RecordingBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
606 const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions,
607 const AudioStreamPacketDescription *inPacketDescs)
608{
609 SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData;
610 SDL_assert(inAQ == device->hidden->audioQueue);
611 SDL_assert(inBuffer != NULL); // ...right?
612 SDL_assert(device->hidden->current_buffer == NULL); // shouldn't have anything pending
613 device->hidden->current_buffer = inBuffer;
614 SDL_RecordingAudioThreadIterate(device);
615
616 // buffer is unexpectedly here? We're probably dying, but try to requeue this buffer anyhow.
617 if (device->hidden->current_buffer != NULL) {
618 SDL_assert(SDL_GetAtomicInt(&device->shutdown) != 0);
619 COREAUDIO_FlushRecording(device); // just flush it manually, which will requeue it.
620 }
621}
622
623static void COREAUDIO_CloseDevice(SDL_AudioDevice *device)
624{
625 if (!device->hidden) {
626 return;
627 }
628
629 // dispose of the audio queue before waiting on the thread, or it might stall for a long time!
630 if (device->hidden->audioQueue) {
631 AudioQueueFlush(device->hidden->audioQueue);
632 AudioQueueStop(device->hidden->audioQueue, 0);
633 AudioQueueDispose(device->hidden->audioQueue, 0);
634 }
635
636 if (device->hidden->thread) {
637 SDL_assert(SDL_GetAtomicInt(&device->shutdown) != 0); // should have been set by SDL_audio.c
638 SDL_WaitThread(device->hidden->thread, NULL);
639 }
640
641 #ifndef MACOSX_COREAUDIO
642 UpdateAudioSession(device, false, true);
643 #endif
644
645 if (device->hidden->ready_semaphore) {
646 SDL_DestroySemaphore(device->hidden->ready_semaphore);
647 }
648
649 // AudioQueueDispose() frees the actual buffer objects.
650 SDL_free(device->hidden->audioBuffer);
651 SDL_free(device->hidden->thread_error);
652 SDL_free(device->hidden);
653}
654
655#ifdef MACOSX_COREAUDIO
656static bool PrepareDevice(SDL_AudioDevice *device)
657{
658 SDL_assert(device->handle != NULL); // this meant "system default" in SDL2, but doesn't anymore
659
660 const SDLCoreAudioHandle *handle = (const SDLCoreAudioHandle *) device->handle;
661 const AudioDeviceID devid = handle->devid;
662 OSStatus result = noErr;
663 UInt32 size = 0;
664
665 AudioObjectPropertyAddress addr = {
666 0,
667 kAudioObjectPropertyScopeGlobal,
668 kAudioObjectPropertyElementMain
669 };
670
671 UInt32 alive = 0;
672 size = sizeof(alive);
673 addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
674 addr.mScope = device->recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
675 result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive);
676 CHECK_RESULT("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
677 if (!alive) {
678 return SDL_SetError("CoreAudio: requested device exists, but isn't alive.");
679 }
680
681 // some devices don't support this property, so errors are fine here.
682 pid_t pid = 0;
683 size = sizeof(pid);
684 addr.mSelector = kAudioDevicePropertyHogMode;
685 result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid);
686 if ((result == noErr) && (pid != -1)) {
687 return SDL_SetError("CoreAudio: requested device is being hogged.");
688 }
689
690 device->hidden->deviceID = devid;
691
692 return true;
693}
694
695static bool AssignDeviceToAudioQueue(SDL_AudioDevice *device)
696{
697 const AudioObjectPropertyAddress prop = {
698 kAudioDevicePropertyDeviceUID,
699 device->recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
700 kAudioObjectPropertyElementMain
701 };
702
703 OSStatus result;
704 CFStringRef devuid;
705 UInt32 devuidsize = sizeof(devuid);
706 result = AudioObjectGetPropertyData(device->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid);
707 CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)");
708 result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize);
709 CFRelease(devuid); // Release devuid; we're done with it and AudioQueueSetProperty should have retained if it wants to keep it.
710 CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)");
711 return true;
712}
713#endif
714
715static bool PrepareAudioQueue(SDL_AudioDevice *device)
716{
717 const AudioStreamBasicDescription *strdesc = &device->hidden->strdesc;
718 const bool recording = device->recording;
719 OSStatus result;
720
721 SDL_assert(CFRunLoopGetCurrent() != NULL);
722
723 if (recording) {
724 result = AudioQueueNewInput(strdesc, RecordingBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue);
725 CHECK_RESULT("AudioQueueNewInput");
726 } else {
727 result = AudioQueueNewOutput(strdesc, PlaybackBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue);
728 CHECK_RESULT("AudioQueueNewOutput");
729 }
730
731 #ifdef MACOSX_COREAUDIO
732 if (!AssignDeviceToAudioQueue(device)) {
733 return false;
734 }
735 #endif
736
737 SDL_UpdatedAudioDeviceFormat(device); // make sure this is correct.
738
739 // Set the channel layout for the audio queue
740 AudioChannelLayout layout;
741 SDL_zero(layout);
742 switch (device->spec.channels) {
743 case 1:
744 // a standard mono stream
745 layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
746 break;
747 case 2:
748 // a standard stereo stream (L R) - implied playback
749 layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
750 break;
751 case 3:
752 // L R LFE
753 layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4;
754 break;
755 case 4:
756 // front left, front right, back left, back right
757 layout.mChannelLayoutTag = kAudioChannelLayoutTag_Quadraphonic;
758 break;
759 case 5:
760 // L R LFE Ls Rs
761 layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_6;
762 break;
763 case 6:
764 // L R C LFE Ls Rs
765 layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_12;
766 break;
767 case 7:
768 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
769 // L R C LFE Cs Ls Rs
770 layout.mChannelLayoutTag = kAudioChannelLayoutTag_WAVE_6_1;
771 } else {
772 // L R C LFE Ls Rs Cs
773 layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A;
774
775 // Convert from SDL channel layout to kAudioChannelLayoutTag_MPEG_6_1_A
776 static const int swizzle_map[7] = {
777 0, 1, 2, 3, 6, 4, 5
778 };
779 device->chmap = SDL_ChannelMapDup(swizzle_map, SDL_arraysize(swizzle_map));
780 if (!device->chmap) {
781 return false;
782 }
783 }
784 break;
785 case 8:
786 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
787 // L R C LFE Rls Rrs Ls Rs
788 layout.mChannelLayoutTag = kAudioChannelLayoutTag_WAVE_7_1;
789 } else {
790 // L R C LFE Ls Rs Rls Rrs
791 layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_C;
792
793 // Convert from SDL channel layout to kAudioChannelLayoutTag_MPEG_7_1_C
794 static const int swizzle_map[8] = {
795 0, 1, 2, 3, 6, 7, 4, 5
796 };
797 device->chmap = SDL_ChannelMapDup(swizzle_map, SDL_arraysize(swizzle_map));
798 if (!device->chmap) {
799 return false;
800 }
801 }
802 break;
803 default:
804 return SDL_SetError("Unsupported audio channels");
805 }
806 if (layout.mChannelLayoutTag != 0) {
807 result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_ChannelLayout, &layout, sizeof(layout));
808 CHECK_RESULT("AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)");
809 }
810
811 // Make sure we can feed the device a minimum amount of time
812 double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
813 #ifdef SDL_PLATFORM_IOS
814 if (SDL_floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
815 // Older iOS hardware, use 40 ms as a minimum time
816 MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0;
817 }
818 #endif
819
820 // we use THREE audio buffers by default, unlike most things that would
821 // choose two alternating buffers, because it helps with issues on
822 // Bluetooth headsets when recording and playing at the same time.
823 // See conversation in #8192 for details.
824 int numAudioBuffers = 3;
825 const double msecs = (device->sample_frames / ((double)device->spec.freq)) * 1000.0;
826 if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { // use more buffers if we have a VERY small sample set.
827 numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
828 }
829
830 device->hidden->numAudioBuffers = numAudioBuffers;
831 device->hidden->audioBuffer = SDL_calloc(numAudioBuffers, sizeof(AudioQueueBufferRef));
832 if (device->hidden->audioBuffer == NULL) {
833 return false;
834 }
835
836 #if DEBUG_COREAUDIO
837 SDL_Log("COREAUDIO: numAudioBuffers == %d", numAudioBuffers);
838 #endif
839
840 for (int i = 0; i < numAudioBuffers; i++) {
841 result = AudioQueueAllocateBuffer(device->hidden->audioQueue, device->buffer_size, &device->hidden->audioBuffer[i]);
842 CHECK_RESULT("AudioQueueAllocateBuffer");
843 SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->silence_value, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity);
844 device->hidden->audioBuffer[i]->mAudioDataByteSize = device->hidden->audioBuffer[i]->mAudioDataBytesCapacity;
845 // !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data?
846 result = AudioQueueEnqueueBuffer(device->hidden->audioQueue, device->hidden->audioBuffer[i], 0, NULL);
847 CHECK_RESULT("AudioQueueEnqueueBuffer");
848 }
849
850 result = AudioQueueStart(device->hidden->audioQueue, NULL);
851 CHECK_RESULT("AudioQueueStart");
852
853 return true; // We're running!
854}
855
856static int AudioQueueThreadEntry(void *arg)
857{
858 SDL_AudioDevice *device = (SDL_AudioDevice *)arg;
859
860 if (device->recording) {
861 SDL_RecordingAudioThreadSetup(device);
862 } else {
863 SDL_PlaybackAudioThreadSetup(device);
864 }
865
866 if (!PrepareAudioQueue(device)) {
867 device->hidden->thread_error = SDL_strdup(SDL_GetError());
868 SDL_SignalSemaphore(device->hidden->ready_semaphore);
869 return 0;
870 }
871
872 // init was successful, alert parent thread and start running...
873 SDL_SignalSemaphore(device->hidden->ready_semaphore);
874
875 // This would be WaitDevice/WaitRecordingDevice in the normal SDL audio thread, but we get *BufferReadyCallback calls here to know when to iterate.
876 while (!SDL_GetAtomicInt(&device->shutdown)) {
877 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
878 }
879
880 if (device->recording) {
881 SDL_RecordingAudioThreadShutdown(device);
882 } else {
883 // Drain off any pending playback.
884 const CFTimeInterval secs = (((CFTimeInterval)device->sample_frames) / ((CFTimeInterval)device->spec.freq)) * 2.0;
885 CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0);
886 SDL_PlaybackAudioThreadShutdown(device);
887 }
888
889 return 0;
890}
891
892static bool COREAUDIO_OpenDevice(SDL_AudioDevice *device)
893{
894 // Initialize all variables that we clean on shutdown
895 device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
896 if (device->hidden == NULL) {
897 return false;
898 }
899
900 #ifndef MACOSX_COREAUDIO
901 if (!UpdateAudioSession(device, true, true)) {
902 return false;
903 }
904
905 // Stop CoreAudio from doing expensive audio rate conversion
906 @autoreleasepool {
907 AVAudioSession *session = [AVAudioSession sharedInstance];
908 [session setPreferredSampleRate:device->spec.freq error:nil];
909 device->spec.freq = (int)session.sampleRate;
910 #ifdef SDL_PLATFORM_TVOS
911 if (device->recording) {
912 [session setPreferredInputNumberOfChannels:device->spec.channels error:nil];
913 device->spec.channels = (int)session.preferredInputNumberOfChannels;
914 } else {
915 [session setPreferredOutputNumberOfChannels:device->spec.channels error:nil];
916 device->spec.channels = (int)session.preferredOutputNumberOfChannels;
917 }
918 #else
919 // Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS
920 #endif // SDL_PLATFORM_TVOS
921 }
922 #endif
923
924 // Setup a AudioStreamBasicDescription with the requested format
925 AudioStreamBasicDescription *strdesc = &device->hidden->strdesc;
926 strdesc->mFormatID = kAudioFormatLinearPCM;
927 strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked;
928 strdesc->mChannelsPerFrame = device->spec.channels;
929 strdesc->mSampleRate = device->spec.freq;
930 strdesc->mFramesPerPacket = 1;
931
932 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
933 SDL_AudioFormat test_format;
934 while ((test_format = *(closefmts++)) != 0) {
935 // CoreAudio handles most of SDL's formats natively.
936 switch (test_format) {
937 case SDL_AUDIO_U8:
938 case SDL_AUDIO_S8:
939 case SDL_AUDIO_S16LE:
940 case SDL_AUDIO_S16BE:
941 case SDL_AUDIO_S32LE:
942 case SDL_AUDIO_S32BE:
943 case SDL_AUDIO_F32LE:
944 case SDL_AUDIO_F32BE:
945 break;
946
947 default:
948 continue;
949 }
950 break;
951 }
952
953 if (!test_format) { // shouldn't happen, but just in case...
954 return SDL_SetError("%s: Unsupported audio format", "coreaudio");
955 }
956 device->spec.format = test_format;
957 strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(test_format);
958 if (SDL_AUDIO_ISBIGENDIAN(test_format)) {
959 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
960 }
961
962 if (SDL_AUDIO_ISFLOAT(test_format)) {
963 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat;
964 } else if (SDL_AUDIO_ISSIGNED(test_format)) {
965 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
966 }
967
968 strdesc->mBytesPerFrame = strdesc->mChannelsPerFrame * strdesc->mBitsPerChannel / 8;
969 strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket;
970
971#ifdef MACOSX_COREAUDIO
972 if (!PrepareDevice(device)) {
973 return false;
974 }
975#endif
976
977 // This has to init in a new thread so it can get its own CFRunLoop. :/
978 device->hidden->ready_semaphore = SDL_CreateSemaphore(0);
979 if (!device->hidden->ready_semaphore) {
980 return false; // oh well.
981 }
982
983 char threadname[64];
984 SDL_GetAudioThreadName(device, threadname, sizeof(threadname));
985 device->hidden->thread = SDL_CreateThread(AudioQueueThreadEntry, threadname, device);
986 if (!device->hidden->thread) {
987 return false;
988 }
989
990 SDL_WaitSemaphore(device->hidden->ready_semaphore);
991 SDL_DestroySemaphore(device->hidden->ready_semaphore);
992 device->hidden->ready_semaphore = NULL;
993
994 if ((device->hidden->thread != NULL) && (device->hidden->thread_error != NULL)) {
995 SDL_WaitThread(device->hidden->thread, NULL);
996 device->hidden->thread = NULL;
997 return SDL_SetError("%s", device->hidden->thread_error);
998 }
999
1000 return (device->hidden->thread != NULL);
1001}
1002
1003static void COREAUDIO_DeinitializeStart(void)
1004{
1005#ifdef MACOSX_COREAUDIO
1006 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL);
1007 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_playback_device_address, DefaultPlaybackDeviceChangedNotification, NULL);
1008 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_recording_device_address, DefaultRecordingDeviceChangedNotification, NULL);
1009#endif
1010}
1011
1012static bool COREAUDIO_Init(SDL_AudioDriverImpl *impl)
1013{
1014 impl->OpenDevice = COREAUDIO_OpenDevice;
1015 impl->PlayDevice = COREAUDIO_PlayDevice;
1016 impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf;
1017 impl->RecordDevice = COREAUDIO_RecordDevice;
1018 impl->FlushRecording = COREAUDIO_FlushRecording;
1019 impl->CloseDevice = COREAUDIO_CloseDevice;
1020 impl->DeinitializeStart = COREAUDIO_DeinitializeStart;
1021
1022#ifdef MACOSX_COREAUDIO
1023 impl->DetectDevices = COREAUDIO_DetectDevices;
1024 impl->FreeDeviceHandle = COREAUDIO_FreeDeviceHandle;
1025#else
1026 impl->OnlyHasDefaultPlaybackDevice = true;
1027 impl->OnlyHasDefaultRecordingDevice = true;
1028#endif
1029
1030 impl->ProvidesOwnCallbackThread = true;
1031 impl->HasRecordingSupport = true;
1032
1033 return true;
1034}
1035
1036AudioBootStrap COREAUDIO_bootstrap = {
1037 "coreaudio", "CoreAudio", COREAUDIO_Init, false, false
1038};
1039
1040#endif // SDL_AUDIO_DRIVER_COREAUDIO