diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 92dd51846..df445a313 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ + sl) { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java index 7f3328673..cc602aeb5 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.java @@ -60,6 +60,7 @@ public final class SettingsFile { public static final String KEY_AUDIO_OUTPUT_ENGINE = "output_engine"; public static final String KEY_ENABLE_AUDIO_STRETCHING = "enable_audio_stretching"; public static final String KEY_VOLUME = "volume"; + public static final String KEY_MIC_INPUT_TYPE = "mic_input_type"; public static final String KEY_USE_VIRTUAL_SD = "use_virtual_sd"; diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index 8fe02f210..04c609dde 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -18,13 +18,15 @@ add_library(main SHARED game_info.h id_cache.cpp id_cache.h + mic.cpp + mic.h native.cpp native.h ndk_motion.cpp ndk_motion.h ) -target_link_libraries(main PRIVATE common core input_common network) +target_link_libraries(main PRIVATE audio_core common core input_common network) target_link_libraries(main PRIVATE android camera2ndk mediandk jnigraphics EGL glad inih log yuv) set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} main) diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 93ff627c7..9451d3439 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -170,7 +170,7 @@ void Config::ReadValues() { Settings::values.mic_input_device = sdl2_config->GetString("Audio", "mic_input_device", "Default"); Settings::values.mic_input_type = - static_cast(sdl2_config->GetInteger("Audio", "mic_input_type", 0)); + static_cast(sdl2_config->GetInteger("Audio", "mic_input_type", 1)); // Data Storage Settings::values.use_virtual_sd = diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index 894a7f8b1..f8f4f4151 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -201,6 +201,10 @@ enable_audio_stretching = # auto (default): Auto-select output_device = +# Which mic input type to use. +# 0: None, 1 (default): Real device, 2: Static noise +mic_input_type = + # Output volume. # 1.0 (default): 100%, 0.0; mute volume = diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index 2ed4bed57..b7197389f 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -26,6 +26,7 @@ static jmethodID s_is_portrait_mode; static jmethodID s_landscape_screen_layout; static jmethodID s_exit_emulation_activity; static jmethodID s_request_camera_permission; +static jmethodID s_request_mic_permission; namespace IDCache { @@ -80,6 +81,10 @@ jmethodID GetRequestCameraPermission() { return s_request_camera_permission; } +jmethodID GetRequestMicPermission() { + return s_request_mic_permission; +} + } // namespace IDCache #ifdef __cplusplus @@ -120,6 +125,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); s_request_camera_permission = env->GetStaticMethodID(s_native_library_class, "RequestCameraPermission", "()Z"); + s_request_mic_permission = + env->GetStaticMethodID(s_native_library_class, "RequestMicPermission", "()Z"); MiiSelector::InitJNI(env); SoftwareKeyboard::InitJNI(env); diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index f210e848c..64c185e80 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h @@ -19,6 +19,7 @@ jmethodID GetIsPortraitMode(); jmethodID GetLandscapeScreenLayout(); jmethodID GetExitEmulationActivity(); jmethodID GetRequestCameraPermission(); +jmethodID GetRequestMicPermission(); } // namespace IDCache diff --git a/src/android/app/src/main/jni/mic.cpp b/src/android/app/src/main/jni/mic.cpp new file mode 100644 index 000000000..90191fbcf --- /dev/null +++ b/src/android/app/src/main/jni/mic.cpp @@ -0,0 +1,38 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/logging/log.h" +#include "jni/id_cache.h" +#include "jni/mic.h" + +#ifdef HAVE_CUBEB +#include "audio_core/cubeb_input.h" +#endif + +namespace Mic { + +AndroidFactory::~AndroidFactory() = default; + +std::unique_ptr AndroidFactory::Create(std::string mic_device_name) { +#ifdef HAVE_CUBEB + if (!permission_granted) { + JNIEnv* env = IDCache::GetEnvForThread(); + permission_granted = env->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(), + IDCache::GetRequestMicPermission()); + } + + if (permission_granted) { + return std::make_unique(std::move(mic_device_name)); + } else { + LOG_WARNING(Frontend, "Mic permissions denied"); + return std::make_unique(); + } +#else + LOG_WARNING(Frontend, "No cubeb support"); + return std::make_unique(); +#endif +} + +} // namespace Mic diff --git a/src/android/app/src/main/jni/mic.h b/src/android/app/src/main/jni/mic.h new file mode 100644 index 000000000..d790d52e5 --- /dev/null +++ b/src/android/app/src/main/jni/mic.h @@ -0,0 +1,21 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/frontend/mic.h" + +namespace Mic { + +class AndroidFactory final : public Frontend::Mic::RealMicFactory { +public: + ~AndroidFactory() override; + + std::unique_ptr Create(std::string mic_device_name) override; + +private: + bool permission_granted = false; +}; + +} // namespace Mic diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index ae23af37a..24c66e187 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -17,6 +17,7 @@ #include "core/core.h" #include "core/frontend/applets/default_applets.h" #include "core/frontend/camera/factory.h" +#include "core/frontend/mic.h" #include "core/frontend/scope_acquire_context.h" #include "core/hle/service/am/am.h" #include "core/hle/service/nfc/nfc.h" @@ -30,6 +31,7 @@ #include "jni/emu_window/emu_window.h" #include "jni/game_info.h" #include "jni/id_cache.h" +#include "jni/mic.h" #include "jni/native.h" #include "jni/ndk_motion.h" #include "video_core/renderer_base.h" @@ -137,6 +139,9 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { system.RegisterMiiSelector(std::make_shared()); system.RegisterSoftwareKeyboard(std::make_shared()); + // Register real Mic factory + Frontend::Mic::RegisterRealMicFactory(std::make_unique()); + InputManager::Init(); window->MakeCurrent(); diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 928b7c024..174151c39 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -141,4 +141,16 @@ 2 3 + + + None + Real Device + Static Noise + + + + 0 + 1 + 2 + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 40bfbee8e..34a76d559 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -99,6 +99,7 @@ Enable audio stretching Stretches audio to reduce stuttering. When enabled, increases audio latency and slightly reduces performance. + Audio Input Device Clear @@ -178,4 +179,8 @@ Select Image Camera Citra needs to access your camera to emulate the 3DS\'s cameras.\n\nAlternatively, you can also set \"Image Source\" to \"Still Image\" in Camera Settings. + + + Microphone + Citra needs to access your microphone to emulate the 3DS\'s microphone.\n\nAlternatively, you can also change \"Audio Input Device\" in Audio Settings. diff --git a/src/audio_core/cubeb_input.cpp b/src/audio_core/cubeb_input.cpp index 04d89e525..91c2013c1 100644 --- a/src/audio_core/cubeb_input.cpp +++ b/src/audio_core/cubeb_input.cpp @@ -189,4 +189,11 @@ std::vector ListCubebInputDevices() { cubeb_destroy(ctx); return device_list; } + +CubebFactory::~CubebFactory() = default; + +std::unique_ptr CubebFactory::Create(std::string mic_device_name) { + return std::make_unique(std::move(mic_device_name)); +} + } // namespace AudioCore diff --git a/src/audio_core/cubeb_input.h b/src/audio_core/cubeb_input.h index 2e752da7a..f81fb5ac2 100644 --- a/src/audio_core/cubeb_input.h +++ b/src/audio_core/cubeb_input.h @@ -31,4 +31,11 @@ private: std::vector ListCubebInputDevices(); +class CubebFactory final : public Frontend::Mic::RealMicFactory { +public: + ~CubebFactory() override; + + std::unique_ptr Create(std::string mic_device_name) override; +}; + } // namespace AudioCore diff --git a/src/core/frontend/mic.cpp b/src/core/frontend/mic.cpp index 543a23767..c9dbc9cc7 100644 --- a/src/core/frontend/mic.cpp +++ b/src/core/frontend/mic.cpp @@ -5,6 +5,10 @@ #include #include "core/frontend/mic.h" +#ifdef HAVE_CUBEB +#include "audio_core/cubeb_input.h" +#endif + namespace Frontend::Mic { constexpr std::array NOISE_SAMPLE_8_BIT = {0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, @@ -57,4 +61,26 @@ Samples StaticMic::Read() { return (sample_size == 8) ? CACHE_8_BIT : CACHE_16_BIT; } +RealMicFactory::~RealMicFactory() = default; + +NullFactory::~NullFactory() = default; + +std::unique_ptr NullFactory::Create([[maybe_unused]] std::string mic_device_name) { + return std::make_unique(); +} + +#ifdef HAVE_CUBEB +static std::unique_ptr g_factory = std::make_unique(); +#else +static std::unique_ptr g_factory = std::make_unique(); +#endif + +void RegisterRealMicFactory(std::unique_ptr factory) { + g_factory = std::move(factory); +} + +std::unique_ptr CreateRealMic(std::string mic_device_name) { + return g_factory->Create(std::move(mic_device_name)); +} + } // namespace Frontend::Mic diff --git a/src/core/frontend/mic.h b/src/core/frontend/mic.h index 1504ad3ca..28738df52 100644 --- a/src/core/frontend/mic.h +++ b/src/core/frontend/mic.h @@ -115,4 +115,23 @@ private: std::vector CACHE_16_BIT; }; +/// Factory for creating a real Mic input device; +class RealMicFactory { +public: + virtual ~RealMicFactory(); + + virtual std::unique_ptr Create(std::string mic_device_name) = 0; +}; + +class NullFactory final : public RealMicFactory { +public: + ~NullFactory() override; + + std::unique_ptr Create(std::string mic_device_name) override; +}; + +void RegisterRealMicFactory(std::unique_ptr factory); + +std::unique_ptr CreateRealMic(std::string mic_device_name); + } // namespace Frontend::Mic diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index de1bfc074..39e375d39 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -3,9 +3,6 @@ // Refer to the license.txt file included. #include -#ifdef HAVE_CUBEB -#include "audio_core/cubeb_input.h" -#endif #include "common/archives.h" #include "common/logging/log.h" #include "core/core.h" @@ -359,11 +356,7 @@ struct MIC_U::Impl { new_mic = std::make_unique(); break; case Settings::MicInputType::Real: -#if HAVE_CUBEB - new_mic = std::make_unique(Settings::values.mic_input_device); -#else - new_mic = std::make_unique(); -#endif + new_mic = Frontend::Mic::CreateRealMic(Settings::values.mic_input_device); break; case Settings::MicInputType::Static: new_mic = std::make_unique();