diff --git a/audio/drivers_microphone/coreaudio_microphone.c b/audio/drivers_microphone/coreaudio_microphone.c
new file mode 100644
index 000000000000..2179d7bcb021
--- /dev/null
+++ b/audio/drivers_microphone/coreaudio_microphone.c
@@ -0,0 +1,214 @@
+/* RetroArch - A frontend for libretro.
+ * Copyright (C) 2023 Jesse Talavera-Greenberg
+ *
+ * RetroArch is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with RetroArch.
+ * If not, see .
+ */
+
+#include "verbosity.h"
+#include "retro_assert.h"
+#include "retro_math.h"
+#include "audio/microphone_driver.h"
+#include
+#include
+
+#ifndef TARGET_OS_IPHONE
+#include
+#endif
+
+#include
+#include
+
+typedef struct
+{
+ bool nonblock;
+} ca_mic_drv_t;
+
+typedef struct
+{
+ AudioQueueRef audioQueue;
+ AudioQueueBufferRef buffers[3];
+ fifo_buffer_t *buffer;
+ slock_t *lock;
+ scond_t *cond;
+ bool running;
+} ca_mic_t;
+
+static void *coreaudio_microphone_init(void)
+{
+ ca_mic_drv_t *drv = (ca_mic_drv_t *)calloc(1, sizeof(*drv));
+ return drv;
+}
+
+static void coreaudio_microphone_free(void *drv)
+{
+ if (drv)
+ free(drv);
+}
+
+static int coreaudio_microphone_read(void *drv, void *m, void *buf, size_t sz)
+{
+ ca_mic_drv_t *cadrv = (ca_mic_drv_t *)drv;
+ ca_mic_t *mic = (ca_mic_t *)m;
+ size_t read = 0;
+ slock_lock(mic->lock);
+ if (cadrv->nonblock)
+ {
+ size_t avail, read_amt;
+ avail = FIFO_READ_AVAIL(mic->buffer);
+ read_amt = avail > sz ? sz : avail;
+ if (read_amt > 0)
+ fifo_read(mic->buffer, buf, read_amt);
+ read = read_amt;
+ }
+ else
+ {
+ while (read < sz)
+ {
+ size_t avail = FIFO_READ_AVAIL(mic->buffer);
+ if (avail)
+ {
+ size_t read_amt = MIN(sz - read, avail);
+ fifo_read(mic->buffer, buf + read, read_amt);
+ read += read_amt;
+ }
+// if (read != sz)
+// scond_wait(mic->cond, mic->lock);
+ }
+ }
+ scond_signal(mic->cond);
+ slock_lock(mic->lock);
+ return (int)read;
+}
+
+static void coreaudio_microphone_set_nonblock_state(void *drv, bool nonblock)
+{
+ ca_mic_drv_t *cadrv = (ca_mic_drv_t *)drv;
+ if (cadrv)
+ cadrv->nonblock = nonblock;
+}
+
+static void AudioInputCallback(void *inUserData, AudioQueueRef inAQ,
+ AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime,
+ UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc)
+{
+ ca_mic_t *mic = (ca_mic_t *)inUserData;
+
+ if (inNumPackets > 0) {
+ size_t read = 0;
+ slock_lock(mic->lock);
+ while (read < inBuffer->mAudioDataByteSize)
+ {
+ if (FIFO_WRITE_AVAIL(mic->buffer) >= inBuffer->mAudioDataByteSize)
+ {
+ fifo_write(mic->buffer, inBuffer->mAudioData, inBuffer->mAudioDataByteSize);
+ read += inBuffer->mAudioDataByteSize;
+ }
+ }
+ scond_signal(mic->cond);
+ slock_unlock(mic->lock);
+ }
+
+ OSStatus status = AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
+ printf("%d\n", status);
+}
+
+static void *coreaudio_microphone_open_mic(void *drv, const char *device, unsigned rate, unsigned latency, unsigned *new_rate)
+{
+ ca_mic_t *mic = (ca_mic_t *)calloc(1, sizeof(*mic));
+ if (!mic)
+ return NULL;
+
+ int frames = next_pow2(rate * latency / 1000);
+ AudioStreamBasicDescription audioFormat;
+ memset(&audioFormat, 0, sizeof(audioFormat));
+ audioFormat.mSampleRate = rate;
+ audioFormat.mFormatID = kAudioFormatLinearPCM;
+ audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
+ audioFormat.mFramesPerPacket = 1;
+ audioFormat.mChannelsPerFrame = 1;
+ audioFormat.mBitsPerChannel = 16;
+ audioFormat.mBytesPerPacket = 2;
+ audioFormat.mBytesPerFrame = 2;
+
+ AudioQueueRef audioQueue;
+ AudioQueueNewInput(&audioFormat, AudioInputCallback, mic, NULL, kCFRunLoopCommonModes, 0, &audioQueue);
+
+ const int bufferByteSize = frames * 2;
+
+ for (int i = 0; i < 3; ++i)
+ AudioQueueAllocateBuffer(audioQueue, bufferByteSize, &mic->buffers[i]);
+
+ mic->audioQueue = audioQueue;
+ mic->buffer = fifo_new(frames << 3);
+ mic->lock = slock_new();
+ mic->cond = scond_new();
+ mic->running = false;
+ return mic;
+}
+
+static void coreaudio_microphone_close_mic(void *drv, void *m)
+{
+ ca_mic_t *mic = (ca_mic_t *)m;
+ AudioQueueDispose(mic->audioQueue, true);
+ fifo_free(mic->buffer);
+ slock_free(mic->lock);
+ scond_free(mic->cond);
+ free(mic);
+}
+
+static bool coreaudio_microphone_mic_alive(const void *drv, const void *m)
+{
+ ca_mic_t *mic = (ca_mic_t *)m;
+ return mic && mic->running;
+}
+
+static bool coreaudio_microphone_start_mic(void *drv, void *m)
+{
+ ca_mic_t *mic = (ca_mic_t *)m;
+ if (!mic->running)
+ {
+ for (int i = 0; i < 3; i++)
+ AudioQueueEnqueueBuffer(mic->audioQueue, mic->buffers[i], 0, NULL);
+ OSStatus status = AudioQueueStart(mic->audioQueue, NULL);
+ mic->running = (status == 0);
+ }
+ return mic->running;
+}
+
+static bool coreaudio_microphone_stop_mic(void *drv, void *m)
+{
+ ca_mic_t *mic = (ca_mic_t *)m;
+ AudioQueueStop(mic->audioQueue, true);
+ mic->running = false;
+ return true;
+}
+
+static bool coreaudio_microphone_mic_use_float(const void *drv, const void *mic)
+{
+ return false;
+}
+
+microphone_driver_t microphone_coreaudio = {
+ coreaudio_microphone_init,
+ coreaudio_microphone_free,
+ coreaudio_microphone_read,
+ coreaudio_microphone_set_nonblock_state,
+ "coreaudio",
+ NULL,
+ NULL,
+ coreaudio_microphone_open_mic,
+ coreaudio_microphone_close_mic,
+ coreaudio_microphone_mic_alive,
+ coreaudio_microphone_start_mic,
+ coreaudio_microphone_stop_mic,
+ coreaudio_microphone_mic_use_float,
+};
diff --git a/audio/microphone_driver.c b/audio/microphone_driver.c
index f5a667cf86f0..f5afa7eb97ec 100644
--- a/audio/microphone_driver.c
+++ b/audio/microphone_driver.c
@@ -56,6 +56,9 @@ microphone_driver_t *microphone_drivers[] = {
#endif
#ifdef HAVE_SDL2
µphone_sdl, /* Microphones are not supported in SDL 1 */
+#endif
+#ifdef HAVE_COREAUDIO
+ µphone_coreaudio,
#endif
µphone_null,
NULL,
diff --git a/audio/microphone_driver.h b/audio/microphone_driver.h
index d9304b48126e..48a73f00ef64 100644
--- a/audio/microphone_driver.h
+++ b/audio/microphone_driver.h
@@ -641,6 +641,11 @@ extern microphone_driver_t microphone_sdl;
*/
extern microphone_driver_t microphone_wasapi;
+/**
+ * The coreaudio-backed microphone driver.
+ */
+extern microphone_driver_t microphone_coreaudio;
+
/**
* @return Pointer to the global microphone driver state.
*/
diff --git a/griffin/griffin.c b/griffin/griffin.c
index 4081b3afb9a0..a4d70c181922 100644
--- a/griffin/griffin.c
+++ b/griffin/griffin.c
@@ -933,6 +933,9 @@ AUDIO
#ifdef HAVE_COREAUDIO
#include "../audio/drivers/coreaudio.c"
+#ifdef HAVE_MICROPHONE
+#include "../audio/drivers_microphone/coreaudio_microphone.c"
+#endif
#endif
#if defined(HAVE_WASAPI) || ((_WIN32_WINNT >= 0x0602) && !defined(__WINRT__))
diff --git a/pkg/apple/BaseConfig.xcconfig b/pkg/apple/BaseConfig.xcconfig
index b4eeae517fc2..7a0f2c5770bd 100644
--- a/pkg/apple/BaseConfig.xcconfig
+++ b/pkg/apple/BaseConfig.xcconfig
@@ -52,6 +52,7 @@ OTHER_CFLAGS = $(inherited) -DHAVE_MATERIALUI
OTHER_CFLAGS = $(inherited) -DHAVE_MENU
OTHER_CFLAGS = $(inherited) -DHAVE_METAL
OTHER_CFLAGS = $(inherited) -DHAVE_MFI
+OTHER_CFLAGS = $(inherited) -DHAVE_MICROPHONE
OTHER_CFLAGS = $(inherited) -DHAVE_MMAP
OTHER_CFLAGS = $(inherited) -DHAVE_NETPLAYDISCOVERY
OTHER_CFLAGS = $(inherited) -DHAVE_NETPLAYDISCOVERY_NSNET
diff --git a/pkg/apple/OSX/Info_Metal.plist b/pkg/apple/OSX/Info_Metal.plist
index 01e4a10ec1f3..48ee9dea9b68 100644
--- a/pkg/apple/OSX/Info_Metal.plist
+++ b/pkg/apple/OSX/Info_Metal.plist
@@ -66,6 +66,8 @@
Copyright © 2024 RetroArch. All rights reserved.
NSMainNibFile
MainMenu_Metal
+ NSMicrophoneUsageDescription
+ We use it.
NSPrincipalClass
NSApplication
RAPortableInstall
diff --git a/pkg/apple/RetroArch.entitlements b/pkg/apple/RetroArch.entitlements
index 3b95f077c972..c3499043dc41 100644
--- a/pkg/apple/RetroArch.entitlements
+++ b/pkg/apple/RetroArch.entitlements
@@ -8,5 +8,7 @@
com.apple.security.cs.disable-library-validation
+ com.apple.security.device.audio-input
+
diff --git a/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj b/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj
index f7021dae6949..6c3eaf83a01d 100644
--- a/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj
+++ b/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj
@@ -70,6 +70,8 @@
0790F67B2BF282B400AA58C9 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0790F6782BF282B400AA58C9 /* Media.xcassets */; };
0790F67C2BF2925400AA58C9 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0790F6782BF282B400AA58C9 /* Media.xcassets */; };
0795A8C7299A095300D5035D /* CoreHaptics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0795A8C6299A095300D5035D /* CoreHaptics.framework */; };
+ 07AC0F542A7215E200016C17 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07AC0F532A7215E200016C17 /* CoreMedia.framework */; };
+ 07AC0F552A72160500016C17 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07AC0F532A7215E200016C17 /* CoreMedia.framework */; };
07EF0FF62BEB114000EDCA9B /* MoltenVK.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07EF0FF42BEB114000EDCA9B /* MoltenVK.xcframework */; };
07EF0FF92BEB117000EDCA9B /* MoltenVK.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07EF0FF42BEB114000EDCA9B /* MoltenVK.xcframework */; };
07EF0FFA2BEB117000EDCA9B /* MoltenVK.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 07EF0FF42BEB114000EDCA9B /* MoltenVK.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -101,8 +103,6 @@
07F2BBE02BE83A4700FD1295 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84DD5EB21A89E6C0007336C1 /* AudioUnit.framework */; };
07F2BBE12BE83A4700FD1295 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B97324FDCFA39411CA2CEA /* AppKit.framework */; };
07F2BBE22BE83A4700FD1295 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84DD5EB41A89E737007336C1 /* IOKit.framework */; };
- 07AC0F542A7215E200016C17 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07AC0F532A7215E200016C17 /* CoreMedia.framework */; };
- 07AC0F552A72160500016C17 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07AC0F532A7215E200016C17 /* CoreMedia.framework */; };
5061C8A41AE47E510080AE14 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5061C8A31AE47E510080AE14 /* libz.dylib */; };
509F0C9D1AA23AFC00619ECC /* griffin_objc.m in Sources */ = {isa = PBXBuildFile; fileRef = 509F0C9C1AA23AFC00619ECC /* griffin_objc.m */; };
840222FC1A889EE2009AB261 /* griffin.c in Sources */ = {isa = PBXBuildFile; fileRef = 840222FB1A889EE2009AB261 /* griffin.c */; settings = {COMPILER_FLAGS = "-include $(DERIVED_FILE_DIR)/git_version.h"; }; };
@@ -563,11 +563,11 @@
0776EF3829A005D600AF0237 /* Steam.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Steam.xcconfig; sourceTree = ""; };
0790F6782BF282B400AA58C9 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Media.xcassets; path = OSX/Media.xcassets; sourceTree = ""; };
0795A8C6299A095300D5035D /* CoreHaptics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreHaptics.framework; path = System/Library/Frameworks/CoreHaptics.framework; sourceTree = SDKROOT; };
+ 07AC0F532A7215E200016C17 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
07EF0FF42BEB114000EDCA9B /* MoltenVK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MoltenVK.xcframework; path = Frameworks/MoltenVK.xcframework; sourceTree = ""; };
07F2BBC22BE83A4200FD1295 /* AppStore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppStore.xcconfig; sourceTree = ""; };
07F2BBE92BE83A4700FD1295 /* RetroArch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RetroArch.app; sourceTree = BUILT_PRODUCTS_DIR; };
07F8037C2BEFE4BD000FD557 /* RetroArchAppStore.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RetroArchAppStore.entitlements; sourceTree = ""; };
- 07AC0F532A7215E200016C17 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
089C165DFE840E0CC02AAC07 /* InfoPlist.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = InfoPlist.strings; path = OSX/en.lproj/InfoPlist.strings; sourceTree = ""; };
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; };
5061C8A31AE47E510080AE14 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
diff --git a/pkg/apple/RetroArch_iOS13.xcodeproj/project.pbxproj b/pkg/apple/RetroArch_iOS13.xcodeproj/project.pbxproj
index c4336e329687..a0df22eae69e 100644
--- a/pkg/apple/RetroArch_iOS13.xcodeproj/project.pbxproj
+++ b/pkg/apple/RetroArch_iOS13.xcodeproj/project.pbxproj
@@ -2392,7 +2392,6 @@
"-DHAVE_GLSL",
"-DHAVE_GLSLANG",
"-DHAVE_GRIFFIN",
- "-DHAVE_VULKAN",
"-DHAVE_HID",
"-DHAVE_IFINFO",
"-DHAVE_IMAGEVIEWER",
@@ -2403,6 +2402,7 @@
"-DHAVE_MENU",
"-DHAVE_METAL",
"-DHAVE_MFI",
+ "-DHAVE_MICROPHONE",
"-DHAVE_MINIUPNPC",
"-DHAVE_NETPLAYDISCOVERY",
"-DHAVE_NETPLAYDISCOVERY_NSNET",
@@ -2437,6 +2437,7 @@
"-DHAVE_UPDATE_ASSETS",
"-DHAVE_UPDATE_CORE_INFO",
"-DHAVE_VIDEO_FILTER",
+ "-DHAVE_VULKAN",
"-DHAVE_XDELTA",
"-DHAVE_XMB",
"-DHAVE_ZLIB",
@@ -2521,7 +2522,6 @@
"-DHAVE_GLSL",
"-DHAVE_GLSLANG",
"-DHAVE_GRIFFIN",
- "-DHAVE_VULKAN",
"-DHAVE_HID",
"-DHAVE_IFINFO",
"-DHAVE_IMAGEVIEWER",
@@ -2532,6 +2532,7 @@
"-DHAVE_MENU",
"-DHAVE_METAL",
"-DHAVE_MFI",
+ "-DHAVE_MICROPHONE",
"-DHAVE_MINIUPNPC",
"-DHAVE_NETPLAYDISCOVERY",
"-DHAVE_NETPLAYDISCOVERY_NSNET",
@@ -2566,6 +2567,7 @@
"-DHAVE_UPDATE_ASSETS",
"-DHAVE_UPDATE_CORE_INFO",
"-DHAVE_VIDEO_FILTER",
+ "-DHAVE_VULKAN",
"-DHAVE_XDELTA",
"-DHAVE_XMB",
"-DHAVE_ZLIB",