From 070e4d2fd2a12cfcb0733678e574b36655f3e836 Mon Sep 17 00:00:00 2001 From: Eric Warmenhoven Date: Wed, 17 Jan 2024 10:09:02 -0500 Subject: [PATCH] WIP - coreaudio microphone --- .../drivers_microphone/coreaudio_microphone.c | 214 ++++++++++++++++++ audio/microphone_driver.c | 3 + audio/microphone_driver.h | 5 + griffin/griffin.c | 3 + pkg/apple/BaseConfig.xcconfig | 1 + pkg/apple/OSX/Info_Metal.plist | 2 + pkg/apple/RetroArch.entitlements | 2 + .../RetroArch_Metal.xcodeproj/project.pbxproj | 6 +- .../RetroArch_iOS13.xcodeproj/project.pbxproj | 6 +- 9 files changed, 237 insertions(+), 5 deletions(-) create mode 100644 audio/drivers_microphone/coreaudio_microphone.c 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",