diff options
Diffstat (limited to 'source/3rd-party/SDL2/src/audio/coreaudio/SDL_coreaudio.m')
-rw-r--r-- | source/3rd-party/SDL2/src/audio/coreaudio/SDL_coreaudio.m | 861 |
1 files changed, 861 insertions, 0 deletions
diff --git a/source/3rd-party/SDL2/src/audio/coreaudio/SDL_coreaudio.m b/source/3rd-party/SDL2/src/audio/coreaudio/SDL_coreaudio.m new file mode 100644 index 0000000..59242f9 --- /dev/null +++ b/source/3rd-party/SDL2/src/audio/coreaudio/SDL_coreaudio.m @@ -0,0 +1,861 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org> + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#if SDL_AUDIO_DRIVER_COREAUDIO + +/* !!! FIXME: clean out some of the macro salsa in here. */ + +#include "SDL_audio.h" +#include "SDL_hints.h" +#include "SDL_timer.h" +#include "../SDL_audio_c.h" +#include "../SDL_sysaudio.h" +#include "SDL_coreaudio.h" +#include "SDL_assert.h" +#include "../../thread/SDL_systhread.h" + +#define DEBUG_COREAUDIO 0 + +#define CHECK_RESULT(msg) \ + if (result != noErr) { \ + SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \ + return 0; \ + } + +#if MACOSX_COREAUDIO +static const AudioObjectPropertyAddress devlist_address = { + kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster +}; + +typedef void (*addDevFn)(const char *name, const int iscapture, AudioDeviceID devId, void *data); + +typedef struct AudioDeviceList +{ + AudioDeviceID devid; + SDL_bool alive; + struct AudioDeviceList *next; +} AudioDeviceList; + +static AudioDeviceList *output_devs = NULL; +static AudioDeviceList *capture_devs = NULL; + +static SDL_bool +add_to_internal_dev_list(const int iscapture, AudioDeviceID devId) +{ + AudioDeviceList *item = (AudioDeviceList *) SDL_malloc(sizeof (AudioDeviceList)); + if (item == NULL) { + return SDL_FALSE; + } + item->devid = devId; + item->alive = SDL_TRUE; + item->next = iscapture ? capture_devs : output_devs; + if (iscapture) { + capture_devs = item; + } else { + output_devs = item; + } + + return SDL_TRUE; +} + +static void +addToDevList(const char *name, const int iscapture, AudioDeviceID devId, void *data) +{ + if (add_to_internal_dev_list(iscapture, devId)) { + SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId)); + } +} + +static void +build_device_list(int iscapture, addDevFn addfn, void *addfndata) +{ + OSStatus result = noErr; + UInt32 size = 0; + AudioDeviceID *devs = NULL; + UInt32 i = 0; + UInt32 max = 0; + + result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, + &devlist_address, 0, NULL, &size); + if (result != kAudioHardwareNoError) + return; + + devs = (AudioDeviceID *) alloca(size); + if (devs == NULL) + return; + + result = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &devlist_address, 0, NULL, &size, devs); + if (result != kAudioHardwareNoError) + return; + + max = size / sizeof (AudioDeviceID); + for (i = 0; i < max; i++) { + CFStringRef cfstr = NULL; + char *ptr = NULL; + AudioDeviceID dev = devs[i]; + AudioBufferList *buflist = NULL; + int usable = 0; + CFIndex len = 0; + const AudioObjectPropertyAddress addr = { + kAudioDevicePropertyStreamConfiguration, + iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + + const AudioObjectPropertyAddress nameaddr = { + kAudioObjectPropertyName, + iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + + result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size); + if (result != noErr) + continue; + + buflist = (AudioBufferList *) SDL_malloc(size); + if (buflist == NULL) + continue; + + result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, + &size, buflist); + + if (result == noErr) { + UInt32 j; + for (j = 0; j < buflist->mNumberBuffers; j++) { + if (buflist->mBuffers[j].mNumberChannels > 0) { + usable = 1; + break; + } + } + } + + SDL_free(buflist); + + if (!usable) + continue; + + + size = sizeof (CFStringRef); + result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr); + if (result != kAudioHardwareNoError) + continue; + + len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), + kCFStringEncodingUTF8); + + ptr = (char *) SDL_malloc(len + 1); + usable = ((ptr != NULL) && + (CFStringGetCString + (cfstr, ptr, len + 1, kCFStringEncodingUTF8))); + + CFRelease(cfstr); + + if (usable) { + len = strlen(ptr); + /* Some devices have whitespace at the end...trim it. */ + while ((len > 0) && (ptr[len - 1] == ' ')) { + len--; + } + usable = (len > 0); + } + + if (usable) { + ptr[len] = '\0'; + +#if DEBUG_COREAUDIO + printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n", + ((iscapture) ? "capture" : "output"), + (int) i, ptr, (int) dev); +#endif + addfn(ptr, iscapture, dev, addfndata); + } + SDL_free(ptr); /* addfn() would have copied the string. */ + } +} + +static void +free_audio_device_list(AudioDeviceList **list) +{ + AudioDeviceList *item = *list; + while (item) { + AudioDeviceList *next = item->next; + SDL_free(item); + item = next; + } + *list = NULL; +} + +static void +COREAUDIO_DetectDevices(void) +{ + build_device_list(SDL_TRUE, addToDevList, NULL); + build_device_list(SDL_FALSE, addToDevList, NULL); +} + +static void +build_device_change_list(const char *name, const int iscapture, AudioDeviceID devId, void *data) +{ + AudioDeviceList **list = (AudioDeviceList **) data; + AudioDeviceList *item; + for (item = *list; item != NULL; item = item->next) { + if (item->devid == devId) { + item->alive = SDL_TRUE; + return; + } + } + + add_to_internal_dev_list(iscapture, devId); /* new device, add it. */ + SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId)); +} + +static void +reprocess_device_list(const int iscapture, AudioDeviceList **list) +{ + AudioDeviceList *item; + AudioDeviceList *prev = NULL; + for (item = *list; item != NULL; item = item->next) { + item->alive = SDL_FALSE; + } + + build_device_list(iscapture, build_device_change_list, list); + + /* free items in the list that aren't still alive. */ + item = *list; + while (item != NULL) { + AudioDeviceList *next = item->next; + if (item->alive) { + prev = item; + } else { + SDL_RemoveAudioDevice(iscapture, (void *) ((size_t) item->devid)); + if (prev) { + prev->next = item->next; + } else { + *list = item->next; + } + SDL_free(item); + } + item = next; + } +} + +/* this is called when the system's list of available audio devices changes. */ +static OSStatus +device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) +{ + reprocess_device_list(SDL_TRUE, &capture_devs); + reprocess_device_list(SDL_FALSE, &output_devs); + return 0; +} +#endif + + +static int open_playback_devices = 0; +static int open_capture_devices = 0; + +#if !MACOSX_COREAUDIO + +static void interruption_begin(_THIS) +{ + if (this != NULL && this->hidden->audioQueue != NULL) { + this->hidden->interrupted = SDL_TRUE; + AudioQueuePause(this->hidden->audioQueue); + } +} + +static void interruption_end(_THIS) +{ + if (this != NULL && this->hidden != NULL && this->hidden->audioQueue != NULL + && this->hidden->interrupted + && AudioQueueStart(this->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) { + this->hidden->interrupted = SDL_FALSE; + } +} + +@interface SDLInterruptionListener : NSObject + +@property (nonatomic, assign) SDL_AudioDevice *device; + +@end + +@implementation SDLInterruptionListener + +- (void)audioSessionInterruption:(NSNotification *)note +{ + @synchronized (self) { + NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey]; + if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) { + interruption_begin(self.device); + } else { + interruption_end(self.device); + } + } +} + +- (void)applicationBecameActive:(NSNotification *)note +{ + @synchronized (self) { + interruption_end(self.device); + } +} + +@end + +static BOOL update_audio_session(_THIS, SDL_bool open) +{ + @autoreleasepool { + AVAudioSession *session = [AVAudioSession sharedInstance]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + /* Set category to ambient by default so that other music continues playing. */ + NSString *category = AVAudioSessionCategoryAmbient; + NSError *err = nil; + + if (open_playback_devices && open_capture_devices) { + category = AVAudioSessionCategoryPlayAndRecord; + } else if (open_capture_devices) { + category = AVAudioSessionCategoryRecord; + } else { + const char *hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY); + if (hint) { + if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) { + category = AVAudioSessionCategoryAmbient; + } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) { + category = AVAudioSessionCategorySoloAmbient; + } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 || + SDL_strcasecmp(hint, "playback") == 0) { + category = AVAudioSessionCategoryPlayback; + } + } + } + + if (![session setCategory:category error:&err]) { + NSString *desc = err.description; + SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); + return NO; + } + + if (open && (open_playback_devices + open_capture_devices) == 1) { + if (![session setActive:YES error:&err]) { + NSString *desc = err.description; + SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String); + return NO; + } + } else if (!open_playback_devices && !open_capture_devices) { + [session setActive:NO error:nil]; + } + + if (open) { + SDLInterruptionListener *listener = [SDLInterruptionListener new]; + listener.device = this; + + [center addObserver:listener + selector:@selector(audioSessionInterruption:) + name:AVAudioSessionInterruptionNotification + object:session]; + + /* An interruption end notification is not guaranteed to be sent if + we were previously interrupted... resuming if needed when the app + becomes active seems to be the way to go. */ + [center addObserver:listener + selector:@selector(applicationBecameActive:) + name:UIApplicationDidBecomeActiveNotification + object:session]; + + [center addObserver:listener + selector:@selector(applicationBecameActive:) + name:UIApplicationWillEnterForegroundNotification + object:session]; + + this->hidden->interruption_listener = CFBridgingRetain(listener); + } else { + if (this->hidden->interruption_listener != NULL) { + SDLInterruptionListener *listener = nil; + listener = (SDLInterruptionListener *) CFBridgingRelease(this->hidden->interruption_listener); + [center removeObserver:listener]; + @synchronized (listener) { + listener.device = NULL; + } + } + } + } + + return YES; +} +#endif + + +/* The AudioQueue callback */ +static void +outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) +{ + SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; + SDL_assert(inBuffer->mAudioDataBytesCapacity == this->hidden->bufferSize); + SDL_memcpy(inBuffer->mAudioData, this->hidden->buffer, this->hidden->bufferSize); + SDL_memset(this->hidden->buffer, '\0', this->hidden->bufferSize); /* zero out in case we have to fill again without new data. */ + inBuffer->mAudioDataByteSize = this->hidden->bufferSize; + AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); + this->hidden->refill = SDL_TRUE; +} + +static Uint8 * +COREAUDIO_GetDeviceBuf(_THIS) +{ + return this->hidden->buffer; +} + +static void +COREAUDIO_WaitDevice(_THIS) +{ + while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); + } + this->hidden->refill = SDL_FALSE; +} + +static void +inputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, + const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions, + const AudioStreamPacketDescription *inPacketDescs ) +{ + SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; + if (SDL_AtomicGet(&this->enabled)) { + SDL_AudioStream *stream = this->hidden->capturestream; + if (SDL_AudioStreamPut(stream, inBuffer->mAudioData, inBuffer->mAudioDataByteSize) == -1) { + /* yikes, out of memory or something. I guess drop the buffer. Our WASAPI target kills the device in this case, though */ + } + AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); + this->hidden->refill = SDL_TRUE; + } +} + +static int +COREAUDIO_CaptureFromDevice(_THIS, void *buffer, int buflen) +{ + SDL_AudioStream *stream = this->hidden->capturestream; + while (SDL_AtomicGet(&this->enabled)) { + const int avail = SDL_AudioStreamAvailable(stream); + if (avail > 0) { + const int cpy = SDL_min(buflen, avail); + SDL_AudioStreamGet(stream, buffer, cpy); + return cpy; + } + + /* wait for more data, try again. */ + while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); + } + this->hidden->refill = SDL_FALSE; + } + + return 0; /* not enabled, giving up. */ +} + +static void +COREAUDIO_FlushCapture(_THIS) +{ + while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 1) == kCFRunLoopRunHandledSource) { + /* spin. */ + } + this->hidden->refill = SDL_FALSE; + SDL_AudioStreamClear(this->hidden->capturestream); +} + + +#if MACOSX_COREAUDIO +static const AudioObjectPropertyAddress alive_address = +{ + kAudioDevicePropertyDeviceIsAlive, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster +}; + +static OSStatus +device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) +{ + SDL_AudioDevice *this = (SDL_AudioDevice *) data; + SDL_bool dead = SDL_FALSE; + UInt32 isAlive = 1; + UInt32 size = sizeof (isAlive); + OSStatus error; + + if (!SDL_AtomicGet(&this->enabled)) { + return 0; /* already known to be dead. */ + } + + error = AudioObjectGetPropertyData(this->hidden->deviceID, &alive_address, + 0, NULL, &size, &isAlive); + + if (error == kAudioHardwareBadDeviceError) { + dead = SDL_TRUE; /* device was unplugged. */ + } else if ((error == kAudioHardwareNoError) && (!isAlive)) { + dead = SDL_TRUE; /* device died in some other way. */ + } + + if (dead) { + SDL_OpenedAudioDeviceDisconnected(this); + } + + return 0; +} +#endif + +static void +COREAUDIO_CloseDevice(_THIS) +{ + const SDL_bool iscapture = this->iscapture; + +/* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ +/* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ +#if MACOSX_COREAUDIO + /* Fire a callback if the device stops being "alive" (disconnected, etc). */ + AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this); +#endif + +#if !MACOSX_COREAUDIO + update_audio_session(this, SDL_FALSE); +#endif + + if (this->hidden->audioQueue) { + AudioQueueDispose(this->hidden->audioQueue, 1); + } + + if (this->hidden->capturestream) { + SDL_FreeAudioStream(this->hidden->capturestream); + } + + /* AudioQueueDispose() frees the actual buffer objects. */ + SDL_free(this->hidden->audioBuffer); + SDL_free(this->hidden->buffer); + SDL_free(this->hidden); + + if (iscapture) { + open_capture_devices--; + } else { + open_playback_devices--; + } +} + +#if MACOSX_COREAUDIO +static int +prepare_device(_THIS, void *handle, int iscapture) +{ + AudioDeviceID devid = (AudioDeviceID) ((size_t) handle); + OSStatus result = noErr; + UInt32 size = 0; + UInt32 alive = 0; + pid_t pid = 0; + + AudioObjectPropertyAddress addr = { + 0, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + if (handle == NULL) { + size = sizeof (AudioDeviceID); + addr.mSelector = + ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice : + kAudioHardwarePropertyDefaultOutputDevice); + result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, + 0, NULL, &size, &devid); + CHECK_RESULT("AudioHardwareGetProperty (default device)"); + } + + addr.mSelector = kAudioDevicePropertyDeviceIsAlive; + addr.mScope = iscapture ? kAudioDevicePropertyScopeInput : + kAudioDevicePropertyScopeOutput; + + size = sizeof (alive); + result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive); + CHECK_RESULT + ("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)"); + + if (!alive) { + SDL_SetError("CoreAudio: requested device exists, but isn't alive."); + return 0; + } + + addr.mSelector = kAudioDevicePropertyHogMode; + size = sizeof (pid); + result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid); + + /* some devices don't support this property, so errors are fine here. */ + if ((result == noErr) && (pid != -1)) { + SDL_SetError("CoreAudio: requested device is being hogged."); + return 0; + } + + this->hidden->deviceID = devid; + return 1; +} +#endif + + +/* this all happens in the audio thread, since it needs a separate runloop. */ +static int +prepare_audioqueue(_THIS) +{ + const AudioStreamBasicDescription *strdesc = &this->hidden->strdesc; + const int iscapture = this->iscapture; + OSStatus result; + int i; + + SDL_assert(CFRunLoopGetCurrent() != NULL); + + if (iscapture) { + result = AudioQueueNewInput(strdesc, inputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue); + CHECK_RESULT("AudioQueueNewInput"); + } else { + result = AudioQueueNewOutput(strdesc, outputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue); + CHECK_RESULT("AudioQueueNewOutput"); + } + +#if MACOSX_COREAUDIO +{ + const AudioObjectPropertyAddress prop = { + kAudioDevicePropertyDeviceUID, + iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + CFStringRef devuid; + UInt32 devuidsize = sizeof (devuid); + result = AudioObjectGetPropertyData(this->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid); + CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)"); + result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize); + CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)"); + + /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ + /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ + /* Fire a callback if the device stops being "alive" (disconnected, etc). */ + AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this); +} +#endif + + /* Make sure we can feed the device a minimum amount of time */ + double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0; +#if defined(__IPHONEOS__) + if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) { + /* Older iOS hardware, use 40 ms as a minimum time */ + MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0; + } +#endif + const double msecs = (this->spec.samples / ((double) this->spec.freq)) * 1000.0; + int numAudioBuffers = 2; + if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { /* use more buffers if we have a VERY small sample set. */ + numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2); + } + + this->hidden->numAudioBuffers = numAudioBuffers; + this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers); + if (this->hidden->audioBuffer == NULL) { + SDL_OutOfMemory(); + return 0; + } + +#if DEBUG_COREAUDIO + printf("COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers); +#endif + + for (i = 0; i < numAudioBuffers; i++) { + result = AudioQueueAllocateBuffer(this->hidden->audioQueue, this->spec.size, &this->hidden->audioBuffer[i]); + CHECK_RESULT("AudioQueueAllocateBuffer"); + SDL_memset(this->hidden->audioBuffer[i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[i]->mAudioDataBytesCapacity); + this->hidden->audioBuffer[i]->mAudioDataByteSize = this->hidden->audioBuffer[i]->mAudioDataBytesCapacity; + result = AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i], 0, NULL); + CHECK_RESULT("AudioQueueEnqueueBuffer"); + } + + result = AudioQueueStart(this->hidden->audioQueue, NULL); + CHECK_RESULT("AudioQueueStart"); + + /* We're running! */ + return 1; +} + +static void +COREAUDIO_ThreadInit(_THIS) +{ + const int rc = prepare_audioqueue(this); + if (!rc) { + /* !!! FIXME: do this in RunAudio, and maybe block OpenDevice until ThreadInit finishes, too, to report an opening error */ + SDL_OpenedAudioDeviceDisconnected(this); /* oh well. */ + } +} + +static void +COREAUDIO_PrepareToClose(_THIS) +{ + /* run long enough to queue some silence, so we know our actual audio + has been played */ + CFRunLoopRunInMode(kCFRunLoopDefaultMode, (((this->spec.samples * 1000) / this->spec.freq) * 2) / 1000.0f, 0); + AudioQueueStop(this->hidden->audioQueue, 1); +} + +static int +COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture) +{ + AudioStreamBasicDescription *strdesc; + SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); + int valid_datatype = 0; + + /* Initialize all variables that we clean on shutdown */ + this->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc((sizeof *this->hidden)); + if (this->hidden == NULL) { + return SDL_OutOfMemory(); + } + SDL_zerop(this->hidden); + + strdesc = &this->hidden->strdesc; + + if (iscapture) { + open_capture_devices++; + } else { + open_playback_devices++; + } + +#if !MACOSX_COREAUDIO + if (!update_audio_session(this, SDL_TRUE)) { + return -1; + } + + /* Stop CoreAudio from doing expensive audio rate conversion */ + @autoreleasepool { + AVAudioSession* session = [AVAudioSession sharedInstance]; + [session setPreferredSampleRate:this->spec.freq error:nil]; + this->spec.freq = (int)session.sampleRate; + } +#endif + + /* Setup a AudioStreamBasicDescription with the requested format */ + SDL_zerop(strdesc); + strdesc->mFormatID = kAudioFormatLinearPCM; + strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked; + strdesc->mChannelsPerFrame = this->spec.channels; + strdesc->mSampleRate = this->spec.freq; + strdesc->mFramesPerPacket = 1; + + while ((!valid_datatype) && (test_format)) { + this->spec.format = test_format; + /* Just a list of valid SDL formats, so people don't pass junk here. */ + switch (test_format) { + case AUDIO_U8: + case AUDIO_S8: + case AUDIO_U16LSB: + case AUDIO_S16LSB: + case AUDIO_U16MSB: + case AUDIO_S16MSB: + case AUDIO_S32LSB: + case AUDIO_S32MSB: + case AUDIO_F32LSB: + case AUDIO_F32MSB: + valid_datatype = 1; + strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format); + if (SDL_AUDIO_ISBIGENDIAN(this->spec.format)) + strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; + + if (SDL_AUDIO_ISFLOAT(this->spec.format)) + strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat; + else if (SDL_AUDIO_ISSIGNED(this->spec.format)) + strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + break; + } + } + + if (!valid_datatype) { /* shouldn't happen, but just in case... */ + return SDL_SetError("Unsupported audio format"); + } + + strdesc->mBytesPerFrame = strdesc->mBitsPerChannel * strdesc->mChannelsPerFrame / 8; + strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket; + +#if MACOSX_COREAUDIO + if (!prepare_device(this, handle, iscapture)) { + return -1; + } +#endif + + /* Calculate the final parameters for this audio specification */ + SDL_CalculateAudioSpec(&this->spec); + + if (iscapture) { + this->hidden->capturestream = SDL_NewAudioStream(this->spec.format, this->spec.channels, this->spec.freq, this->spec.format, this->spec.channels, this->spec.freq); + if (!this->hidden->capturestream) { + return -1; /* already set SDL_Error */ + } + } else { + this->hidden->bufferSize = this->spec.size; + this->hidden->buffer = SDL_malloc(this->hidden->bufferSize); + if (this->hidden->buffer == NULL) { + return SDL_OutOfMemory(); + } + } + + return 0; +} + +static void +COREAUDIO_Deinitialize(void) +{ +#if MACOSX_COREAUDIO + AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL); + free_audio_device_list(&capture_devs); + free_audio_device_list(&output_devs); +#endif +} + +static int +COREAUDIO_Init(SDL_AudioDriverImpl * impl) +{ + /* Set the function pointers */ + impl->OpenDevice = COREAUDIO_OpenDevice; + impl->CloseDevice = COREAUDIO_CloseDevice; + impl->Deinitialize = COREAUDIO_Deinitialize; + impl->ThreadInit = COREAUDIO_ThreadInit; + impl->WaitDevice = COREAUDIO_WaitDevice; + impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf; + impl->PrepareToClose = COREAUDIO_PrepareToClose; + impl->CaptureFromDevice = COREAUDIO_CaptureFromDevice; + impl->FlushCapture = COREAUDIO_FlushCapture; + +#if MACOSX_COREAUDIO + impl->DetectDevices = COREAUDIO_DetectDevices; + AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL); +#else + impl->OnlyHasDefaultOutputDevice = 1; + impl->OnlyHasDefaultCaptureDevice = 1; +#endif + + impl->HasCaptureSupport = 1; + + return 1; /* this audio target is available. */ +} + +AudioBootStrap COREAUDIO_bootstrap = { + "coreaudio", "CoreAudio", COREAUDIO_Init, 0 +}; + +#endif /* SDL_AUDIO_DRIVER_COREAUDIO */ + +/* vi: set ts=4 sw=4 expandtab: */ |