audio: Implemented capture support for Mac OS X CoreAudio.

I don't know what iOS wants yet, so this code might work there, too...?
This commit is contained in:
Ryan C. Gordon 2016-08-01 00:20:47 -04:00
parent ee09975007
commit 015dd8dd1d
2 changed files with 118 additions and 25 deletions

View File

@ -185,7 +185,7 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
#if DEBUG_COREAUDIO #if DEBUG_COREAUDIO
printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n", printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n",
((iscapture) ? "capture" : "output"), ((iscapture) ? "capture" : "output"),
(int) *devCount, ptr, (int) dev); (int) i, ptr, (int) dev);
#endif #endif
addfn(ptr, iscapture, dev, addfndata); addfn(ptr, iscapture, dev, addfndata);
} }
@ -324,18 +324,52 @@ outputCallback(void *inRefCon,
} }
} }
return 0; return noErr;
} }
static OSStatus static OSStatus
inputCallback(void *inRefCon, inputCallback(void *inRefCon,
AudioUnitRenderActionFlags * ioActionFlags, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp * inTimeStamp, const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber, UInt32 inNumberFrames, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList * ioData) AudioBufferList *ioData)
{ {
/* err = AudioUnitRender(afr->fAudioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, afr->fAudioBuffer); */ SDL_AudioDevice *this = (SDL_AudioDevice *) inRefCon;
/* !!! FIXME: write me! */ if (!this->enabled || 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;
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; return noErr;
} }
@ -394,20 +428,21 @@ COREAUDIO_CloseDevice(_THIS)
const int iscapture = this->iscapture; const int iscapture = this->iscapture;
const AudioUnitElement bus = const AudioUnitElement bus =
((iscapture) ? input_bus : output_bus); ((iscapture) ? input_bus : output_bus);
const AudioUnitScope scope =
((iscapture) ? kAudioUnitScope_Output :
kAudioUnitScope_Input);
/* stop processing the audio unit */ /* stop processing the audio unit */
AudioOutputUnitStop(this->hidden->audioUnit); AudioOutputUnitStop(this->hidden->audioUnit);
/* Remove the input callback */ /* Remove the input callback */
SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct)); SDL_zero(callback);
AudioUnitSetProperty(this->hidden->audioUnit, AudioUnitSetProperty(this->hidden->audioUnit,
kAudioUnitProperty_SetRenderCallback, iscapture ? kAudioOutputUnitProperty_SetInputCallback : kAudioUnitProperty_SetRenderCallback,
scope, bus, &callback, sizeof(callback)); kAudioUnitScope_Global, bus, &callback, sizeof(callback));
AudioComponentInstanceDispose(this->hidden->audioUnit); AudioComponentInstanceDispose(this->hidden->audioUnit);
this->hidden->audioUnitOpened = 0; this->hidden->audioUnitOpened = 0;
#if MACOSX_COREAUDIO
SDL_free(this->hidden->captureBufferList.mBuffers[0].mData);
#endif
} }
SDL_free(this->hidden->buffer); SDL_free(this->hidden->buffer);
SDL_free(this->hidden); SDL_free(this->hidden);
@ -480,9 +515,6 @@ prepare_audiounit(_THIS, void *handle, int iscapture,
AudioComponent comp = NULL; AudioComponent comp = NULL;
const AudioUnitElement output_bus = 0; const AudioUnitElement output_bus = 0;
const AudioUnitElement input_bus = 1; 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 MACOSX_COREAUDIO
if (!prepare_device(this, handle, iscapture)) { if (!prepare_device(this, handle, iscapture)) {
@ -495,7 +527,7 @@ prepare_audiounit(_THIS, void *handle, int iscapture,
desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentManufacturer = kAudioUnitManufacturer_Apple;
#if MACOSX_COREAUDIO #if MACOSX_COREAUDIO
desc.componentSubType = kAudioUnitSubType_DefaultOutput; desc.componentSubType = iscapture ? kAudioUnitSubType_HALOutput : kAudioUnitSubType_DefaultOutput;
#else #else
desc.componentSubType = kAudioUnitSubType_RemoteIO; desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif #endif
@ -513,9 +545,28 @@ prepare_audiounit(_THIS, void *handle, int iscapture,
this->hidden->audioUnitOpened = 1; this->hidden->audioUnitOpened = 1;
#if MACOSX_COREAUDIO #if MACOSX_COREAUDIO
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)");
}
/* this is always on the output_bus, even for capture devices. */
result = AudioUnitSetProperty(this->hidden->audioUnit, result = AudioUnitSetProperty(this->hidden->audioUnit,
kAudioOutputUnitProperty_CurrentDevice, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, kAudioUnitScope_Global, output_bus,
&this->hidden->deviceID, &this->hidden->deviceID,
sizeof(AudioDeviceID)); sizeof(AudioDeviceID));
CHECK_RESULT CHECK_RESULT
@ -525,16 +576,47 @@ prepare_audiounit(_THIS, void *handle, int iscapture,
/* Set the data format of the audio unit. */ /* Set the data format of the audio unit. */
result = AudioUnitSetProperty(this->hidden->audioUnit, result = AudioUnitSetProperty(this->hidden->audioUnit,
kAudioUnitProperty_StreamFormat, kAudioUnitProperty_StreamFormat,
scope, bus, strdesc, sizeof(*strdesc)); iscapture ? kAudioUnitScope_Output : kAudioUnitScope_Input,
iscapture ? input_bus : output_bus,
strdesc, sizeof (*strdesc));
CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)"); CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)");
#if MACOSX_COREAUDIO
if (iscapture) { /* only need to do this for capture devices. */
void *ptr;
UInt32 framesize = 0;
UInt32 propsize = sizeof (UInt32);
result = AudioUnitGetProperty(this->hidden->audioUnit,
kAudioDevicePropertyBufferFrameSize,
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) {
COREAUDIO_CloseDevice(this);
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;
}
#endif
/* Set the audio callback */ /* Set the audio callback */
SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct)); SDL_zero(callback);
callback.inputProc = ((iscapture) ? inputCallback : outputCallback); callback.inputProc = ((iscapture) ? inputCallback : outputCallback);
callback.inputProcRefCon = this; callback.inputProcRefCon = this;
result = AudioUnitSetProperty(this->hidden->audioUnit, result = AudioUnitSetProperty(this->hidden->audioUnit,
kAudioUnitProperty_SetRenderCallback, iscapture ? kAudioOutputUnitProperty_SetInputCallback : kAudioUnitProperty_SetRenderCallback,
scope, bus, &callback, sizeof(callback)); kAudioUnitScope_Global, output_bus, &callback, sizeof(callback));
CHECK_RESULT CHECK_RESULT
("AudioUnitSetProperty (kAudioUnitProperty_SetRenderCallback)"); ("AudioUnitSetProperty (kAudioUnitProperty_SetRenderCallback)");
@ -542,8 +624,15 @@ prepare_audiounit(_THIS, void *handle, int iscapture,
SDL_CalculateAudioSpec(&this->spec); SDL_CalculateAudioSpec(&this->spec);
/* Allocate a sample buffer */ /* Allocate a sample buffer */
this->hidden->bufferOffset = this->hidden->bufferSize = this->spec.size; this->hidden->bufferSize = this->spec.size;
this->hidden->bufferOffset = iscapture ? 0 : this->hidden->bufferSize;
this->hidden->buffer = SDL_malloc(this->hidden->bufferSize); this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
if (this->hidden->buffer == NULL) {
COREAUDIO_CloseDevice(this);
SDL_OutOfMemory();
return 0;
}
result = AudioUnitInitialize(this->hidden->audioUnit); result = AudioUnitInitialize(this->hidden->audioUnit);
CHECK_RESULT("AudioUnitInitialize"); CHECK_RESULT("AudioUnitInitialize");
@ -552,6 +641,8 @@ prepare_audiounit(_THIS, void *handle, int iscapture,
result = AudioOutputUnitStart(this->hidden->audioUnit); result = AudioOutputUnitStart(this->hidden->audioUnit);
CHECK_RESULT("AudioOutputUnitStart"); 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 #if MACOSX_COREAUDIO
/* Fire a callback if the device stops being "alive" (disconnected, etc). */ /* Fire a callback if the device stops being "alive" (disconnected, etc). */
AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this); AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
@ -575,10 +666,10 @@ COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
if (this->hidden == NULL) { if (this->hidden == NULL) {
return SDL_OutOfMemory(); return SDL_OutOfMemory();
} }
SDL_memset(this->hidden, 0, (sizeof *this->hidden)); SDL_zerop(this->hidden);
/* Setup a AudioStreamBasicDescription with the requested format */ /* Setup a AudioStreamBasicDescription with the requested format */
SDL_memset(&strdesc, '\0', sizeof(AudioStreamBasicDescription)); SDL_zero(strdesc);
strdesc.mFormatID = kAudioFormatLinearPCM; strdesc.mFormatID = kAudioFormatLinearPCM;
strdesc.mFormatFlags = kLinearPCMFormatFlagIsPacked; strdesc.mFormatFlags = kLinearPCMFormatFlagIsPacked;
strdesc.mChannelsPerFrame = this->spec.channels; strdesc.mChannelsPerFrame = this->spec.channels;
@ -651,6 +742,7 @@ COREAUDIO_Init(SDL_AudioDriverImpl * impl)
#if MACOSX_COREAUDIO #if MACOSX_COREAUDIO
impl->DetectDevices = COREAUDIO_DetectDevices; impl->DetectDevices = COREAUDIO_DetectDevices;
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL); AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
impl->HasCaptureSupport = 1;
#else #else
impl->OnlyHasDefaultOutputDevice = 1; impl->OnlyHasDefaultOutputDevice = 1;

View File

@ -50,6 +50,7 @@ struct SDL_PrivateAudioData
UInt32 bufferSize; UInt32 bufferSize;
#if MACOSX_COREAUDIO #if MACOSX_COREAUDIO
AudioDeviceID deviceID; AudioDeviceID deviceID;
AudioBufferList captureBufferList;
#endif #endif
}; };