Commit 015dd8dd1d9db9cbf4db174a3e842824b1d4d8e5

Ryan C. Gordon 2016-08-01T00:20:47

audio: Implemented capture support for Mac OS X CoreAudio. I don't know what iOS wants yet, so this code might work there, too...?

diff --git a/src/audio/coreaudio/SDL_coreaudio.c b/src/audio/coreaudio/SDL_coreaudio.c
index 3641d53..3bf66d6 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 577f9fb..95e1215 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
 };