mirror of
https://github.com/encounter/SDL.git
synced 2025-05-20 06:11:22 +00:00
563 lines
18 KiB
C
563 lines
18 KiB
C
/*
|
|
Simple DirectMedia Layer
|
|
Copyright (C) 1997-2014 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
|
|
|
|
#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
|
|
|
|
static void COREAUDIO_CloseDevice(_THIS);
|
|
|
|
#define CHECK_RESULT(msg) \
|
|
if (result != noErr) { \
|
|
COREAUDIO_CloseDevice(this); \
|
|
SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \
|
|
return 0; \
|
|
}
|
|
|
|
#if MACOSX_COREAUDIO
|
|
typedef void (*addDevFn)(const char *name, AudioDeviceID devId, void *data);
|
|
|
|
static void
|
|
addToDevList(const char *name, AudioDeviceID devId, void *data)
|
|
{
|
|
SDL_AddAudioDevice addfn = (SDL_AddAudioDevice) data;
|
|
addfn(name);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
const char *findname;
|
|
AudioDeviceID devId;
|
|
int found;
|
|
} FindDevIdData;
|
|
|
|
static void
|
|
findDevId(const char *name, AudioDeviceID devId, void *_data)
|
|
{
|
|
FindDevIdData *data = (FindDevIdData *) _data;
|
|
if (!data->found) {
|
|
if (SDL_strcmp(name, data->findname) == 0) {
|
|
data->found = 1;
|
|
data->devId = 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;
|
|
|
|
AudioObjectPropertyAddress addr = {
|
|
kAudioHardwarePropertyDevices,
|
|
kAudioObjectPropertyScopeGlobal,
|
|
kAudioObjectPropertyElementMaster
|
|
};
|
|
|
|
result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr,
|
|
0, NULL, &size);
|
|
if (result != kAudioHardwareNoError)
|
|
return;
|
|
|
|
devs = (AudioDeviceID *) alloca(size);
|
|
if (devs == NULL)
|
|
return;
|
|
|
|
result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
|
|
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;
|
|
|
|
addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
|
|
kAudioDevicePropertyScopeOutput;
|
|
addr.mSelector = kAudioDevicePropertyStreamConfiguration;
|
|
|
|
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;
|
|
|
|
addr.mSelector = kAudioObjectPropertyName;
|
|
size = sizeof (CFStringRef);
|
|
result = AudioObjectGetPropertyData(dev, &addr, 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) *devCount, ptr, (int) dev);
|
|
#endif
|
|
addfn(ptr, dev, addfndata);
|
|
}
|
|
SDL_free(ptr); /* addfn() would have copied the string. */
|
|
}
|
|
}
|
|
|
|
static void
|
|
COREAUDIO_DetectDevices(int iscapture, SDL_AddAudioDevice addfn)
|
|
{
|
|
build_device_list(iscapture, addToDevList, addfn);
|
|
}
|
|
|
|
static int
|
|
find_device_by_name(_THIS, const char *devname, int iscapture)
|
|
{
|
|
AudioDeviceID devid = 0;
|
|
OSStatus result = noErr;
|
|
UInt32 size = 0;
|
|
UInt32 alive = 0;
|
|
pid_t pid = 0;
|
|
|
|
AudioObjectPropertyAddress addr = {
|
|
0,
|
|
kAudioObjectPropertyScopeGlobal,
|
|
kAudioObjectPropertyElementMaster
|
|
};
|
|
|
|
if (devname == NULL) {
|
|
size = sizeof (AudioDeviceID);
|
|
addr.mSelector =
|
|
((iscapture) ? kAudioHardwarePropertyDefaultInputDevice :
|
|
kAudioHardwarePropertyDefaultOutputDevice);
|
|
result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
|
|
0, NULL, &size, &devid);
|
|
CHECK_RESULT("AudioHardwareGetProperty (default device)");
|
|
} else {
|
|
FindDevIdData data;
|
|
SDL_zero(data);
|
|
data.findname = devname;
|
|
build_device_list(iscapture, findDevId, &data);
|
|
if (!data.found) {
|
|
SDL_SetError("CoreAudio: No such audio device.");
|
|
return 0;
|
|
}
|
|
devid = data.devId;
|
|
}
|
|
|
|
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
|
|
|
|
/* 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 (!this->enabled || 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 0;
|
|
}
|
|
|
|
static OSStatus
|
|
inputCallback(void *inRefCon,
|
|
AudioUnitRenderActionFlags * ioActionFlags,
|
|
const AudioTimeStamp * inTimeStamp,
|
|
UInt32 inBusNumber, UInt32 inNumberFrames,
|
|
AudioBufferList * ioData)
|
|
{
|
|
/* err = AudioUnitRender(afr->fAudioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, afr->fAudioBuffer); */
|
|
/* !!! FIXME: write me! */
|
|
return noErr;
|
|
}
|
|
|
|
|
|
static void
|
|
COREAUDIO_CloseDevice(_THIS)
|
|
{
|
|
if (this->hidden != NULL) {
|
|
if (this->hidden->audioUnitOpened) {
|
|
AURenderCallbackStruct callback;
|
|
const AudioUnitElement output_bus = 0;
|
|
const AudioUnitElement input_bus = 1;
|
|
const int iscapture = this->iscapture;
|
|
const AudioUnitElement bus =
|
|
((iscapture) ? input_bus : output_bus);
|
|
const AudioUnitScope scope =
|
|
((iscapture) ? kAudioUnitScope_Output :
|
|
kAudioUnitScope_Input);
|
|
|
|
/* stop processing the audio unit */
|
|
AudioOutputUnitStop(this->hidden->audioUnit);
|
|
|
|
/* Remove the input callback */
|
|
SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct));
|
|
AudioUnitSetProperty(this->hidden->audioUnit,
|
|
kAudioUnitProperty_SetRenderCallback,
|
|
scope, bus, &callback, sizeof(callback));
|
|
|
|
#if MACOSX_COREAUDIO
|
|
CloseComponent(this->hidden->audioUnit);
|
|
#else
|
|
AudioComponentInstanceDispose(this->hidden->audioUnit);
|
|
#endif
|
|
|
|
this->hidden->audioUnitOpened = 0;
|
|
}
|
|
SDL_free(this->hidden->buffer);
|
|
SDL_free(this->hidden);
|
|
this->hidden = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
prepare_audiounit(_THIS, const char *devname, int iscapture,
|
|
const AudioStreamBasicDescription * strdesc)
|
|
{
|
|
OSStatus result = noErr;
|
|
AURenderCallbackStruct callback;
|
|
#if MACOSX_COREAUDIO
|
|
ComponentDescription desc;
|
|
Component comp = NULL;
|
|
#else
|
|
AudioComponentDescription desc;
|
|
AudioComponent comp = NULL;
|
|
#endif
|
|
const AudioUnitElement output_bus = 0;
|
|
const AudioUnitElement input_bus = 1;
|
|
const AudioUnitElement bus = ((iscapture) ? input_bus : output_bus);
|
|
const AudioUnitScope scope = ((iscapture) ? kAudioUnitScope_Output :
|
|
kAudioUnitScope_Input);
|
|
|
|
#if MACOSX_COREAUDIO
|
|
if (!find_device_by_name(this, devname, iscapture)) {
|
|
SDL_SetError("Couldn't find requested CoreAudio device");
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
SDL_zero(desc);
|
|
desc.componentType = kAudioUnitType_Output;
|
|
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
|
|
|
#if MACOSX_COREAUDIO
|
|
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
|
comp = FindNextComponent(NULL, &desc);
|
|
#else
|
|
desc.componentSubType = kAudioUnitSubType_RemoteIO;
|
|
comp = AudioComponentFindNext(NULL, &desc);
|
|
#endif
|
|
|
|
if (comp == NULL) {
|
|
SDL_SetError("Couldn't find requested CoreAudio component");
|
|
return 0;
|
|
}
|
|
|
|
/* Open & initialize the audio unit */
|
|
#if MACOSX_COREAUDIO
|
|
result = OpenAComponent(comp, &this->hidden->audioUnit);
|
|
CHECK_RESULT("OpenAComponent");
|
|
#else
|
|
/*
|
|
AudioComponentInstanceNew only available on iPhone OS 2.0 and Mac OS X 10.6
|
|
We can't use OpenAComponent on iPhone because it is not present
|
|
*/
|
|
result = AudioComponentInstanceNew(comp, &this->hidden->audioUnit);
|
|
CHECK_RESULT("AudioComponentInstanceNew");
|
|
#endif
|
|
|
|
this->hidden->audioUnitOpened = 1;
|
|
|
|
#if MACOSX_COREAUDIO
|
|
result = AudioUnitSetProperty(this->hidden->audioUnit,
|
|
kAudioOutputUnitProperty_CurrentDevice,
|
|
kAudioUnitScope_Global, 0,
|
|
&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,
|
|
scope, bus, strdesc, sizeof(*strdesc));
|
|
CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)");
|
|
|
|
/* Set the audio callback */
|
|
SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct));
|
|
callback.inputProc = ((iscapture) ? inputCallback : outputCallback);
|
|
callback.inputProcRefCon = this;
|
|
result = AudioUnitSetProperty(this->hidden->audioUnit,
|
|
kAudioUnitProperty_SetRenderCallback,
|
|
scope, 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->bufferOffset = this->hidden->bufferSize = this->spec.size;
|
|
this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
|
|
|
|
result = AudioUnitInitialize(this->hidden->audioUnit);
|
|
CHECK_RESULT("AudioUnitInitialize");
|
|
|
|
/* Finally, start processing of the audio unit */
|
|
result = AudioOutputUnitStart(this->hidden->audioUnit);
|
|
CHECK_RESULT("AudioOutputUnitStart");
|
|
|
|
/* We're running! */
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int
|
|
COREAUDIO_OpenDevice(_THIS, 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_memset(this->hidden, 0, (sizeof *this->hidden));
|
|
|
|
/* Setup a AudioStreamBasicDescription with the requested format */
|
|
SDL_memset(&strdesc, '\0', sizeof(AudioStreamBasicDescription));
|
|
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... */
|
|
COREAUDIO_CloseDevice(this);
|
|
return SDL_SetError("Unsupported audio format");
|
|
}
|
|
|
|
strdesc.mBytesPerFrame =
|
|
strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8;
|
|
strdesc.mBytesPerPacket =
|
|
strdesc.mBytesPerFrame * strdesc.mFramesPerPacket;
|
|
|
|
if (!prepare_audiounit(this, devname, iscapture, &strdesc)) {
|
|
COREAUDIO_CloseDevice(this);
|
|
return -1; /* prepare_audiounit() will call SDL_SetError()... */
|
|
}
|
|
|
|
return 0; /* good to go. */
|
|
}
|
|
|
|
static int
|
|
COREAUDIO_Init(SDL_AudioDriverImpl * impl)
|
|
{
|
|
/* Set the function pointers */
|
|
impl->OpenDevice = COREAUDIO_OpenDevice;
|
|
impl->CloseDevice = COREAUDIO_CloseDevice;
|
|
|
|
#if MACOSX_COREAUDIO
|
|
impl->DetectDevices = COREAUDIO_DetectDevices;
|
|
#else
|
|
impl->OnlyHasDefaultOutputDevice = 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.
|
|
*/
|
|
AudioSessionInitialize(NULL, NULL, NULL, nil);
|
|
UInt32 category = kAudioSessionCategory_AmbientSound;
|
|
AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(UInt32), &category);
|
|
#endif
|
|
|
|
impl->ProvidesOwnCallbackThread = 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: */
|