mirror of
https://github.com/Ryujinx/SDL.git
synced 2025-03-27 02:45:13 +00:00
796 lines
26 KiB
C
796 lines
26 KiB
C
/*
|
|
Simple DirectMedia Layer
|
|
Copyright (C) 1997-2016 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_audio_c.h"
|
|
#include "../SDL_sysaudio.h"
|
|
#include "SDL_coreaudio.h"
|
|
#include "SDL_assert.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;
|
|
|
|
static void update_audio_session()
|
|
{
|
|
#if !MACOSX_COREAUDIO
|
|
/* !!! FIXME: move this to AVAudioSession. This is deprecated, and the new version is available as of (ancient!) iOS 3.0 */
|
|
UInt32 category;
|
|
if (open_playback_devices && open_capture_devices) {
|
|
category = kAudioSessionCategory_PlayAndRecord;
|
|
} else if (open_capture_devices) {
|
|
category = kAudioSessionCategory_RecordAudio;
|
|
} else { /* nothing open, or just playing audio. */
|
|
category = kAudioSessionCategory_AmbientSound;
|
|
}
|
|
AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof (UInt32), &category);
|
|
#endif
|
|
}
|
|
|
|
|
|
/* The CoreAudio callback */
|
|
static OSStatus
|
|
outputCallback(void *inRefCon,
|
|
AudioUnitRenderActionFlags * ioActionFlags,
|
|
const AudioTimeStamp * inTimeStamp,
|
|
UInt32 inBusNumber, UInt32 inNumberFrames,
|
|
AudioBufferList * ioData)
|
|
{
|
|
SDL_AudioDevice *this = (SDL_AudioDevice *) inRefCon;
|
|
AudioBuffer *abuf;
|
|
UInt32 remaining, len;
|
|
void *ptr;
|
|
UInt32 i;
|
|
|
|
/* Only do anything if audio is enabled and not paused */
|
|
if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
|
|
for (i = 0; i < ioData->mNumberBuffers; i++) {
|
|
abuf = &ioData->mBuffers[i];
|
|
SDL_memset(abuf->mData, this->spec.silence, abuf->mDataByteSize);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* No SDL conversion should be needed here, ever, since we accept
|
|
any input format in OpenAudio, and leave the conversion to CoreAudio.
|
|
*/
|
|
/*
|
|
SDL_assert(!this->convert.needed);
|
|
SDL_assert(this->spec.channels == ioData->mNumberChannels);
|
|
*/
|
|
|
|
for (i = 0; i < ioData->mNumberBuffers; i++) {
|
|
abuf = &ioData->mBuffers[i];
|
|
remaining = abuf->mDataByteSize;
|
|
ptr = abuf->mData;
|
|
while (remaining > 0) {
|
|
if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
|
|
/* Generate the data */
|
|
SDL_LockMutex(this->mixer_lock);
|
|
(*this->spec.callback)(this->spec.userdata,
|
|
this->hidden->buffer, this->hidden->bufferSize);
|
|
SDL_UnlockMutex(this->mixer_lock);
|
|
this->hidden->bufferOffset = 0;
|
|
}
|
|
|
|
len = this->hidden->bufferSize - this->hidden->bufferOffset;
|
|
if (len > remaining)
|
|
len = remaining;
|
|
SDL_memcpy(ptr, (char *)this->hidden->buffer +
|
|
this->hidden->bufferOffset, len);
|
|
ptr = (char *)ptr + len;
|
|
remaining -= len;
|
|
this->hidden->bufferOffset += len;
|
|
}
|
|
}
|
|
|
|
return noErr;
|
|
}
|
|
|
|
static OSStatus
|
|
inputCallback(void *inRefCon,
|
|
AudioUnitRenderActionFlags *ioActionFlags,
|
|
const AudioTimeStamp *inTimeStamp,
|
|
UInt32 inBusNumber, UInt32 inNumberFrames,
|
|
AudioBufferList *ioData)
|
|
{
|
|
SDL_AudioDevice *this = (SDL_AudioDevice *) inRefCon;
|
|
if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
|
|
return noErr; /* just drop this if we're not accepting input. */
|
|
}
|
|
|
|
const OSStatus err = AudioUnitRender(this->hidden->audioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &this->hidden->captureBufferList);
|
|
SDL_assert(this->hidden->captureBufferList.mNumberBuffers == 1);
|
|
|
|
if (err == noErr) {
|
|
const AudioBuffer *abuf = &this->hidden->captureBufferList.mBuffers[0];
|
|
UInt32 remaining = abuf->mDataByteSize;
|
|
const Uint8 *ptr = (const Uint8 *) abuf->mData;
|
|
|
|
/* No SDL conversion should be needed here, ever, since we accept
|
|
any input format in OpenAudio, and leave the conversion to CoreAudio.
|
|
*/
|
|
while (remaining > 0) {
|
|
UInt32 len = this->hidden->bufferSize - this->hidden->bufferOffset;
|
|
if (len > remaining)
|
|
len = remaining;
|
|
|
|
/* !!! FIXME: why are we copying here? just pass the buffer to the callback? */
|
|
SDL_memcpy((char *)this->hidden->buffer + this->hidden->bufferOffset, ptr, len);
|
|
ptr += len;
|
|
remaining -= len;
|
|
this->hidden->bufferOffset += len;
|
|
|
|
if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
|
|
SDL_LockMutex(this->mixer_lock);
|
|
(*this->spec.callback)(this->spec.userdata,
|
|
this->hidden->buffer, this->hidden->bufferSize);
|
|
SDL_UnlockMutex(this->mixer_lock);
|
|
this->hidden->bufferOffset = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return noErr;
|
|
}
|
|
|
|
|
|
#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 int iscapture = this->iscapture;
|
|
|
|
if (this->hidden->audioUnitOpened) {
|
|
const AudioUnitElement output_bus = 0;
|
|
const AudioUnitElement input_bus = 1;
|
|
const AudioUnitElement bus = ((iscapture) ? input_bus : output_bus);
|
|
AURenderCallbackStruct callback;
|
|
|
|
#if MACOSX_COREAUDIO
|
|
/* Unregister our disconnect callback. */
|
|
AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
|
|
#endif
|
|
|
|
/* stop processing the audio unit */
|
|
AudioOutputUnitStop(this->hidden->audioUnit);
|
|
|
|
/* Remove the input callback */
|
|
SDL_zero(callback);
|
|
AudioUnitSetProperty(this->hidden->audioUnit,
|
|
iscapture ? kAudioOutputUnitProperty_SetInputCallback : kAudioUnitProperty_SetRenderCallback,
|
|
kAudioUnitScope_Global, bus, &callback, sizeof(callback));
|
|
AudioComponentInstanceDispose(this->hidden->audioUnit);
|
|
}
|
|
|
|
SDL_free(this->hidden->captureBufferList.mBuffers[0].mData);
|
|
SDL_free(this->hidden->buffer);
|
|
SDL_free(this->hidden);
|
|
|
|
if (iscapture) {
|
|
open_capture_devices--;
|
|
} else {
|
|
open_playback_devices--;
|
|
}
|
|
update_audio_session();
|
|
}
|
|
|
|
#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
|
|
|
|
static int
|
|
prepare_audiounit(_THIS, void *handle, int iscapture,
|
|
const AudioStreamBasicDescription * strdesc)
|
|
{
|
|
OSStatus result = noErr;
|
|
AURenderCallbackStruct callback;
|
|
AudioComponentDescription desc;
|
|
AudioComponent comp = NULL;
|
|
const AudioUnitElement output_bus = 0;
|
|
const AudioUnitElement input_bus = 1;
|
|
|
|
#if MACOSX_COREAUDIO
|
|
if (!prepare_device(this, handle, iscapture)) {
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
SDL_zero(desc);
|
|
desc.componentType = kAudioUnitType_Output;
|
|
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
|
|
|
#if MACOSX_COREAUDIO
|
|
desc.componentSubType = iscapture ? kAudioUnitSubType_HALOutput : kAudioUnitSubType_DefaultOutput;
|
|
#else
|
|
desc.componentSubType = kAudioUnitSubType_RemoteIO;
|
|
#endif
|
|
comp = AudioComponentFindNext(NULL, &desc);
|
|
|
|
if (comp == NULL) {
|
|
SDL_SetError("Couldn't find requested CoreAudio component");
|
|
return 0;
|
|
}
|
|
|
|
/* Open & initialize the audio unit */
|
|
result = AudioComponentInstanceNew(comp, &this->hidden->audioUnit);
|
|
CHECK_RESULT("AudioComponentInstanceNew");
|
|
|
|
this->hidden->audioUnitOpened = 1;
|
|
|
|
if (iscapture) { /* have to do EnableIO only for capture devices. */
|
|
UInt32 enable = 1;
|
|
result = AudioUnitSetProperty(this->hidden->audioUnit,
|
|
kAudioOutputUnitProperty_EnableIO,
|
|
kAudioUnitScope_Input, input_bus,
|
|
&enable, sizeof (enable));
|
|
CHECK_RESULT
|
|
("AudioUnitSetProperty (kAudioOutputUnitProperty_EnableIO input bus)");
|
|
|
|
enable = 0;
|
|
result = AudioUnitSetProperty(this->hidden->audioUnit,
|
|
kAudioOutputUnitProperty_EnableIO,
|
|
kAudioUnitScope_Output, output_bus,
|
|
&enable, sizeof (enable));
|
|
CHECK_RESULT
|
|
("AudioUnitSetProperty (kAudioOutputUnitProperty_EnableIO output bus)");
|
|
}
|
|
|
|
#if MACOSX_COREAUDIO
|
|
/* this is always on the output_bus, even for capture devices. */
|
|
result = AudioUnitSetProperty(this->hidden->audioUnit,
|
|
kAudioOutputUnitProperty_CurrentDevice,
|
|
kAudioUnitScope_Global, output_bus,
|
|
&this->hidden->deviceID,
|
|
sizeof(AudioDeviceID));
|
|
CHECK_RESULT
|
|
("AudioUnitSetProperty (kAudioOutputUnitProperty_CurrentDevice)");
|
|
#endif
|
|
|
|
/* Set the data format of the audio unit. */
|
|
result = AudioUnitSetProperty(this->hidden->audioUnit,
|
|
kAudioUnitProperty_StreamFormat,
|
|
iscapture ? kAudioUnitScope_Output : kAudioUnitScope_Input,
|
|
iscapture ? input_bus : output_bus,
|
|
strdesc, sizeof (*strdesc));
|
|
CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)");
|
|
|
|
if (iscapture) { /* only need to do this for capture devices. */
|
|
void *ptr;
|
|
UInt32 framesize = 0;
|
|
UInt32 propsize = sizeof (UInt32);
|
|
|
|
result = AudioUnitGetProperty(this->hidden->audioUnit,
|
|
kAudioUnitProperty_MaximumFramesPerSlice,
|
|
kAudioUnitScope_Global, output_bus,
|
|
&framesize, &propsize);
|
|
CHECK_RESULT
|
|
("AudioUnitGetProperty (kAudioDevicePropertyBufferFrameSize)");
|
|
|
|
framesize *= SDL_AUDIO_BITSIZE(this->spec.format) / 8;
|
|
ptr = SDL_calloc(1, framesize);
|
|
if (ptr == NULL) {
|
|
SDL_OutOfMemory();
|
|
return 0;
|
|
}
|
|
this->hidden->captureBufferList.mNumberBuffers = 1;
|
|
this->hidden->captureBufferList.mBuffers[0].mNumberChannels = this->spec.channels;
|
|
this->hidden->captureBufferList.mBuffers[0].mDataByteSize = framesize;
|
|
this->hidden->captureBufferList.mBuffers[0].mData = ptr;
|
|
}
|
|
|
|
/* Set the audio callback */
|
|
SDL_zero(callback);
|
|
callback.inputProc = ((iscapture) ? inputCallback : outputCallback);
|
|
callback.inputProcRefCon = this;
|
|
|
|
result = AudioUnitSetProperty(this->hidden->audioUnit,
|
|
iscapture ? kAudioOutputUnitProperty_SetInputCallback : kAudioUnitProperty_SetRenderCallback,
|
|
kAudioUnitScope_Global,
|
|
iscapture ? input_bus : output_bus,
|
|
&callback, sizeof (callback));
|
|
CHECK_RESULT
|
|
("AudioUnitSetProperty (kAudioUnitProperty_SetRenderCallback)");
|
|
|
|
/* Calculate the final parameters for this audio specification */
|
|
SDL_CalculateAudioSpec(&this->spec);
|
|
|
|
/* Allocate a sample buffer */
|
|
this->hidden->bufferSize = this->spec.size;
|
|
this->hidden->bufferOffset = iscapture ? 0 : this->hidden->bufferSize;
|
|
|
|
this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
|
|
if (this->hidden->buffer == NULL) {
|
|
SDL_OutOfMemory();
|
|
return 0;
|
|
}
|
|
|
|
result = AudioUnitInitialize(this->hidden->audioUnit);
|
|
CHECK_RESULT("AudioUnitInitialize");
|
|
|
|
/* Finally, start processing of the audio unit */
|
|
result = AudioOutputUnitStart(this->hidden->audioUnit);
|
|
CHECK_RESULT("AudioOutputUnitStart");
|
|
|
|
/* !!! 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). */
|
|
AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
|
|
#endif
|
|
|
|
/* We're running! */
|
|
return 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);
|
|
|
|
if (iscapture) {
|
|
open_capture_devices++;
|
|
} else {
|
|
open_playback_devices++;
|
|
}
|
|
update_audio_session();
|
|
|
|
/* Setup a AudioStreamBasicDescription with the requested format */
|
|
SDL_zero(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 (!prepare_audiounit(this, handle, iscapture, &strdesc)) {
|
|
return -1; /* prepare_audiounit() will call SDL_SetError()... */
|
|
}
|
|
|
|
return 0; /* good to go. */
|
|
}
|
|
|
|
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;
|
|
|
|
#if MACOSX_COREAUDIO
|
|
impl->DetectDevices = COREAUDIO_DetectDevices;
|
|
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
|
|
#else
|
|
impl->OnlyHasDefaultOutputDevice = 1;
|
|
impl->OnlyHasDefaultCaptureDevice = 1;
|
|
|
|
/* Set category to ambient sound so that other music continues playing.
|
|
You can change this at runtime in your own code if you need different
|
|
behavior. If this is common, we can add an SDL hint for this.
|
|
*/
|
|
/* !!! FIXME: move this to AVAudioSession. This is deprecated, and the new version is available as of (ancient!) iOS 3.0 */
|
|
AudioSessionInitialize(NULL, NULL, NULL, nil);
|
|
UInt32 category = kAudioSessionCategory_AmbientSound;
|
|
AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(UInt32), &category);
|
|
#endif
|
|
|
|
impl->ProvidesOwnCallbackThread = 1;
|
|
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: */
|