From 015dd8dd1d9db9cbf4db174a3e842824b1d4d8e5 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 1 Aug 2016 00:20:47 -0400 Subject: [PATCH] audio: Implemented capture support for Mac OS X CoreAudio. I don't know what iOS wants yet, so this code might work there, too...? --- src/audio/coreaudio/SDL_coreaudio.c | 142 +++++++++++++++++++++++----- src/audio/coreaudio/SDL_coreaudio.h | 1 + 2 files changed, 118 insertions(+), 25 deletions(-) diff --git a/src/audio/coreaudio/SDL_coreaudio.c b/src/audio/coreaudio/SDL_coreaudio.c index 3641d530c..3bf66d618 100644 --- a/src/audio/coreaudio/SDL_coreaudio.c +++ b/src/audio/coreaudio/SDL_coreaudio.c @@ -185,7 +185,7 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata) #if DEBUG_COREAUDIO printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n", ((iscapture) ? "capture" : "output"), - (int) *devCount, ptr, (int) dev); + (int) i, ptr, (int) dev); #endif addfn(ptr, iscapture, dev, addfndata); } @@ -324,18 +324,52 @@ outputCallback(void *inRefCon, } } - return 0; + return noErr; } static OSStatus inputCallback(void *inRefCon, - AudioUnitRenderActionFlags * ioActionFlags, - const AudioTimeStamp * inTimeStamp, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList * ioData) + AudioBufferList *ioData) { - /* err = AudioUnitRender(afr->fAudioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, afr->fAudioBuffer); */ - /* !!! FIXME: write me! */ + SDL_AudioDevice *this = (SDL_AudioDevice *) inRefCon; + 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; } @@ -394,20 +428,21 @@ COREAUDIO_CloseDevice(_THIS) 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)); + SDL_zero(callback); AudioUnitSetProperty(this->hidden->audioUnit, - kAudioUnitProperty_SetRenderCallback, - scope, bus, &callback, sizeof(callback)); + iscapture ? kAudioOutputUnitProperty_SetInputCallback : kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Global, bus, &callback, sizeof(callback)); AudioComponentInstanceDispose(this->hidden->audioUnit); 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); @@ -480,9 +515,6 @@ prepare_audiounit(_THIS, void *handle, int iscapture, AudioComponent comp = NULL; 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 (!prepare_device(this, handle, iscapture)) { @@ -495,7 +527,7 @@ prepare_audiounit(_THIS, void *handle, int iscapture, desc.componentManufacturer = kAudioUnitManufacturer_Apple; #if MACOSX_COREAUDIO - desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentSubType = iscapture ? kAudioUnitSubType_HALOutput : kAudioUnitSubType_DefaultOutput; #else desc.componentSubType = kAudioUnitSubType_RemoteIO; #endif @@ -513,9 +545,28 @@ prepare_audiounit(_THIS, void *handle, int iscapture, this->hidden->audioUnitOpened = 1; #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, kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, 0, + kAudioUnitScope_Global, output_bus, &this->hidden->deviceID, sizeof(AudioDeviceID)); CHECK_RESULT @@ -525,16 +576,47 @@ prepare_audiounit(_THIS, void *handle, int iscapture, /* Set the data format of the audio unit. */ result = AudioUnitSetProperty(this->hidden->audioUnit, 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)"); +#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 */ - SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct)); + SDL_zero(callback); callback.inputProc = ((iscapture) ? inputCallback : outputCallback); callback.inputProcRefCon = this; + result = AudioUnitSetProperty(this->hidden->audioUnit, - kAudioUnitProperty_SetRenderCallback, - scope, bus, &callback, sizeof(callback)); + iscapture ? kAudioOutputUnitProperty_SetInputCallback : kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Global, output_bus, &callback, sizeof(callback)); CHECK_RESULT ("AudioUnitSetProperty (kAudioUnitProperty_SetRenderCallback)"); @@ -542,8 +624,15 @@ prepare_audiounit(_THIS, void *handle, int iscapture, SDL_CalculateAudioSpec(&this->spec); /* 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); + if (this->hidden->buffer == NULL) { + COREAUDIO_CloseDevice(this); + SDL_OutOfMemory(); + return 0; + } result = AudioUnitInitialize(this->hidden->audioUnit); CHECK_RESULT("AudioUnitInitialize"); @@ -552,6 +641,8 @@ prepare_audiounit(_THIS, void *handle, int iscapture, 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); @@ -575,10 +666,10 @@ COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture) if (this->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_memset(this->hidden, 0, (sizeof *this->hidden)); + SDL_zerop(this->hidden); /* Setup a AudioStreamBasicDescription with the requested format */ - SDL_memset(&strdesc, '\0', sizeof(AudioStreamBasicDescription)); + SDL_zero(strdesc); strdesc.mFormatID = kAudioFormatLinearPCM; strdesc.mFormatFlags = kLinearPCMFormatFlagIsPacked; strdesc.mChannelsPerFrame = this->spec.channels; @@ -651,6 +742,7 @@ COREAUDIO_Init(SDL_AudioDriverImpl * impl) #if MACOSX_COREAUDIO impl->DetectDevices = COREAUDIO_DetectDevices; AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL); + impl->HasCaptureSupport = 1; #else impl->OnlyHasDefaultOutputDevice = 1; diff --git a/src/audio/coreaudio/SDL_coreaudio.h b/src/audio/coreaudio/SDL_coreaudio.h index 577f9fb32..95e121561 100644 --- a/src/audio/coreaudio/SDL_coreaudio.h +++ b/src/audio/coreaudio/SDL_coreaudio.h @@ -50,6 +50,7 @@ struct SDL_PrivateAudioData UInt32 bufferSize; #if MACOSX_COREAUDIO AudioDeviceID deviceID; + AudioBufferList captureBufferList; #endif };