From 055a58f01ef18665a703b9025cb5a2ffc9bb62c1 Mon Sep 17 00:00:00 2001 From: Steveice10 <1269164+Steveice10@users.noreply.github.com> Date: Mon, 1 May 2023 12:17:45 -0700 Subject: [PATCH] audio_core: Implement OpenAL backend (#6450) --- .gitmodules | 3 + CMakeLists.txt | 1 + externals/CMakeLists.txt | 13 ++ externals/openal-soft | 1 + .../ui/SettingsFragmentPresenter.java | 4 +- .../features/settings/utils/SettingsFile.java | 4 +- src/android/app/src/main/jni/CMakeLists.txt | 2 - src/android/app/src/main/jni/config.cpp | 8 +- src/android/app/src/main/jni/default_ini.h | 28 +-- src/android/app/src/main/jni/mic.cpp | 38 ---- src/android/app/src/main/jni/mic.h | 21 --- src/android/app/src/main/jni/native.cpp | 11 +- .../app/src/main/res/values/arrays.xml | 6 +- src/audio_core/CMakeLists.txt | 13 ++ src/audio_core/cubeb_input.cpp | 23 +-- src/audio_core/cubeb_input.h | 16 +- src/audio_core/cubeb_sink.h | 1 + src/audio_core/dsp_interface.cpp | 5 +- src/audio_core/dsp_interface.h | 5 +- src/audio_core/input.h | 85 +++++++++ src/audio_core/input_details.cpp | 108 +++++++++++ src/audio_core/input_details.h | 36 ++++ src/audio_core/null_input.h | 35 ++++ src/audio_core/openal_input.cpp | 133 +++++++++++++ src/audio_core/openal_input.h | 35 ++++ src/audio_core/openal_sink.cpp | 176 ++++++++++++++++++ src/audio_core/openal_sink.h | 30 +++ src/audio_core/sink_details.cpp | 60 +++--- src/audio_core/sink_details.h | 20 +- src/audio_core/static_input.cpp | 42 +++++ src/audio_core/static_input.h | 33 ++++ src/citra/config.cpp | 9 +- src/citra/default_ini.h | 25 ++- src/citra_qt/configuration/config.cpp | 17 +- .../configuration/configure_audio.cpp | 148 +++++++-------- src/citra_qt/configuration/configure_audio.h | 6 +- src/citra_qt/configuration/configure_audio.ui | 106 ++++------- src/citra_qt/main.cpp | 9 + src/common/settings.cpp | 10 +- src/common/settings.h | 16 +- src/core/CMakeLists.txt | 2 - src/core/core.cpp | 4 +- src/core/core.h | 14 ++ src/core/frontend/mic.cpp | 86 --------- src/core/frontend/mic.h | 137 -------------- src/core/hle/service/mic_u.cpp | 29 +-- src/core/hle/service/plgldr/plgldr.cpp | 1 - src/core/telemetry_session.cpp | 3 +- 48 files changed, 1042 insertions(+), 576 deletions(-) create mode 160000 externals/openal-soft delete mode 100644 src/android/app/src/main/jni/mic.cpp delete mode 100644 src/android/app/src/main/jni/mic.h create mode 100644 src/audio_core/input.h create mode 100644 src/audio_core/input_details.cpp create mode 100644 src/audio_core/input_details.h create mode 100644 src/audio_core/null_input.h create mode 100644 src/audio_core/openal_input.cpp create mode 100644 src/audio_core/openal_input.h create mode 100644 src/audio_core/openal_sink.cpp create mode 100644 src/audio_core/openal_sink.h create mode 100644 src/audio_core/static_input.cpp create mode 100644 src/audio_core/static_input.h delete mode 100644 src/core/frontend/mic.cpp delete mode 100644 src/core/frontend/mic.h diff --git a/.gitmodules b/.gitmodules index faa1cb211..c3944b1f8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -64,3 +64,6 @@ [submodule "dds-ktx"] path = externals/dds-ktx url = https://github.com/septag/dds-ktx +[submodule "externals/openal-soft"] + path = externals/openal-soft + url = https://github.com/kcat/openal-soft diff --git a/CMakeLists.txt b/CMakeLists.txt index 42137b8b5..14a1be883 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ if (MSVC) endif() option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) +option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON) CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support" ON "NOT IOS" OFF) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 74852e300..afd6cb01e 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -190,3 +190,16 @@ if(ANDROID) add_subdirectory(libyuv) target_include_directories(yuv INTERFACE ./libyuv/include) endif() + +# OpenAL Soft +if (ENABLE_OPENAL) + set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "") + set(ALSOFT_EXAMPLES OFF CACHE BOOL "") + set(ALSOFT_INSTALL OFF CACHE BOOL "") + set(ALSOFT_INSTALL_CONFIG OFF CACHE BOOL "") + set(ALSOFT_INSTALL_HRTF_DATA OFF CACHE BOOL "") + set(ALSOFT_INSTALL_AMBDEC_PRESETS OFF CACHE BOOL "") + set(ALSOFT_UTILS OFF CACHE BOOL "") + set(LIBTYPE "STATIC" CACHE STRING "") + add_subdirectory(openal-soft EXCLUDE_FROM_ALL) +endif() diff --git a/externals/openal-soft b/externals/openal-soft new file mode 160000 index 000000000..d9fed51aa --- /dev/null +++ b/externals/openal-soft @@ -0,0 +1 @@ +Subproject commit d9fed51aa6391debc31dbbca550f055c980afe70 diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java index cf3ec3fdf..05329bbd1 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.java @@ -400,10 +400,10 @@ public final class SettingsFragmentPresenter { SettingSection audioSection = mSettings.getSection(Settings.SECTION_AUDIO); Setting audioStretch = audioSection.getSetting(SettingsFile.KEY_ENABLE_AUDIO_STRETCHING); - Setting micInputType = audioSection.getSetting(SettingsFile.KEY_MIC_INPUT_TYPE); + Setting audioInputType = audioSection.getSetting(SettingsFile.KEY_AUDIO_INPUT_TYPE); sl.add(new CheckBoxSetting(SettingsFile.KEY_ENABLE_AUDIO_STRETCHING, Settings.SECTION_AUDIO, R.string.audio_stretch, R.string.audio_stretch_description, true, audioStretch)); - sl.add(new SingleChoiceSetting(SettingsFile.KEY_MIC_INPUT_TYPE, Settings.SECTION_AUDIO, R.string.audio_input_type, 0, R.array.audioInputTypeNames, R.array.audioInputTypeValues, 1, micInputType)); + sl.add(new SingleChoiceSetting(SettingsFile.KEY_AUDIO_INPUT_TYPE, Settings.SECTION_AUDIO, R.string.audio_input_type, 0, R.array.audioInputTypeNames, R.array.audioInputTypeValues, 0, audioInputType)); } private void addDebugSettings(ArrayList 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 54d0121fe..f1adea163 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 @@ -75,10 +75,10 @@ public final class SettingsFile { public static final String KEY_PRELOAD_TEXTURES = "preload_textures"; public static final String KEY_ASYNC_CUSTOM_LOADING = "async_custom_loading"; - public static final String KEY_AUDIO_OUTPUT_ENGINE = "output_engine"; + public static final String KEY_AUDIO_OUTPUT_TYPE = "output_type"; 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_AUDIO_INPUT_TYPE = "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 c78047ab8..5a1abc35d 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -25,8 +25,6 @@ add_library(citra-android SHARED game_settings.h id_cache.cpp id_cache.h - mic.cpp - mic.h native.cpp native.h ndk_motion.cpp diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index e56e3cf0f..bb0a89989 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -199,12 +199,12 @@ void Config::ReadValues() { // Audio ReadSetting("Audio", Settings::values.audio_emulation); - ReadSetting("Audio", Settings::values.sink_id); ReadSetting("Audio", Settings::values.enable_audio_stretching); - ReadSetting("Audio", Settings::values.audio_device_id); ReadSetting("Audio", Settings::values.volume); - ReadSetting("Audio", Settings::values.mic_input_device); - ReadSetting("Audio", Settings::values.mic_input_type); + ReadSetting("Audio", Settings::values.output_type); + ReadSetting("Audio", Settings::values.output_device); + ReadSetting("Audio", Settings::values.input_type); + ReadSetting("Audio", Settings::values.input_device); // Data Storage ReadSetting("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 5ddc1cdfd..c75ee63c8 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -226,28 +226,32 @@ enable_dsp_lle = # 0 (default): No, 1: Yes enable_dsp_lle_thread = -# Which audio output engine to use. -# auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available) -output_engine = - # Whether or not to enable the audio-stretching post-processing effect. # This effect adjusts audio speed to match emulation speed and helps prevent audio stutter, # at the cost of increasing audio latency. # 0: No, 1 (default): Yes enable_audio_stretching = -# Which audio device to use. -# 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 = +# Which audio output type to use. +# 0 (default): Auto-select, 1: No audio output, 2: Cubeb (if available), 3: OpenAL (if available), 4: SDL2 (if available) +output_type = + +# Which audio output device to use. +# auto (default): Auto-select +output_device = + +# Which audio input type to use. +# 0 (default): Auto-select, 1: No audio input, 2: Static noise, 3: Cubeb (if available), 4: OpenAL (if available) +input_type = + +# Which audio input device to use. +# auto (default): Auto-select +input_device = + [Data Storage] # Whether to create a virtual SD card. # 1 (default): Yes, 0: No diff --git a/src/android/app/src/main/jni/mic.cpp b/src/android/app/src/main/jni/mic.cpp deleted file mode 100644 index 90191fbcf..000000000 --- a/src/android/app/src/main/jni/mic.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// 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 deleted file mode 100644 index d790d52e5..000000000 --- a/src/android/app/src/main/jni/mic.h +++ /dev/null @@ -1,21 +0,0 @@ -// 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 663e4a83a..43dc55bbd 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -24,7 +24,6 @@ #include "core/core.h" #include "core/frontend/applets/default_applets.h" #include "core/frontend/camera/factory.h" -#include "core/frontend/mic.h" #include "core/hle/service/am/am.h" #include "core/hle/service/nfc/nfc.h" #include "core/savestate.h" @@ -39,7 +38,6 @@ #include "jni/game_settings.h" #include "jni/id_cache.h" #include "jni/input_manager.h" -#include "jni/mic.h" #include "jni/native.h" #include "jni/ndk_motion.h" #include "video_core/renderer_base.h" @@ -134,6 +132,11 @@ static void TryShutdown() { MicroProfileShutdown(); } +static bool CheckMicPermission() { + return IDCache::GetEnvForThread()->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(), + IDCache::GetRequestMicPermission()); +} + static Core::System::ResultStatus RunCitra(const std::string& filepath) { // Citra core only supports a single running instance std::lock_guard lock(running_mutex); @@ -183,8 +186,8 @@ 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()); + // Register microphone permission check + Core::System::GetInstance().RegisterMicPermissionCheck(&CheckMicPermission); InputManager::Init(); diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 9fc74985a..69ae0e0e9 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -143,15 +143,19 @@ + Auto None - Real Device Static Noise + Real Device (Cubeb) + Real Device (OpenAL) 0 1 2 + 3 + 4 diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index d96a1face..76c930496 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -20,18 +20,25 @@ add_library(audio_core STATIC hle/source.h lle/lle.cpp lle/lle.h + input.h + input_details.cpp + input_details.h interpolate.cpp interpolate.h + null_input.h null_sink.h precompiled_headers.h sink.h sink_details.cpp sink_details.h + static_input.cpp + static_input.h time_stretch.cpp time_stretch.h $<$:sdl2_sink.cpp sdl2_sink.h> $<$:cubeb_sink.cpp cubeb_sink.h cubeb_input.cpp cubeb_input.h> + $<$:openal_input.cpp openal_input.h openal_sink.cpp openal_sink.h> ) create_target_directory_groups(audio_core) @@ -92,6 +99,12 @@ if(ENABLE_CUBEB) target_compile_definitions(audio_core PUBLIC HAVE_CUBEB) endif() +if(ENABLE_OPENAL) + target_link_libraries(audio_core PRIVATE OpenAL) + target_compile_definitions(audio_core PUBLIC HAVE_OPENAL) + add_definitions(-DAL_LIBTYPE_STATIC) +endif() + if (CITRA_USE_PRECOMPILED_HEADERS) target_precompile_headers(audio_core PRIVATE precompiled_headers.h) endif() diff --git a/src/audio_core/cubeb_input.cpp b/src/audio_core/cubeb_input.cpp index abb42ac40..3a42ea974 100644 --- a/src/audio_core/cubeb_input.cpp +++ b/src/audio_core/cubeb_input.cpp @@ -6,11 +6,14 @@ #include #include #include "audio_core/cubeb_input.h" +#include "audio_core/input.h" +#include "audio_core/sink.h" #include "common/logging/log.h" +#include "common/threadsafe_queue.h" namespace AudioCore { -using SampleQueue = Common::SPSCQueue; +using SampleQueue = Common::SPSCQueue; struct CubebInput::Impl { cubeb* ctx = nullptr; @@ -48,10 +51,10 @@ CubebInput::~CubebInput() { cubeb_destroy(impl->ctx); } -void CubebInput::StartSampling(const Frontend::Mic::Parameters& params) { +void CubebInput::StartSampling(const InputParameters& params) { // Cubeb apparently only supports signed 16 bit PCM (and float32 which the 3ds doesn't support) // TODO resample the input stream - if (params.sign == Frontend::Mic::Signedness::Unsigned) { + if (params.sign == Signedness::Unsigned) { LOG_ERROR(Audio, "Application requested unsupported unsigned pcm format. Falling back to signed"); } @@ -62,7 +65,7 @@ void CubebInput::StartSampling(const Frontend::Mic::Parameters& params) { is_sampling = true; cubeb_devid input_device = nullptr; - if (device_id != Frontend::Mic::default_device_name && !device_id.empty()) { + if (device_id != auto_device_name && !device_id.empty()) { cubeb_device_collection collection; if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) != CUBEB_OK) { LOG_WARNING(Audio, "Audio input device enumeration not supported"); @@ -122,9 +125,9 @@ void CubebInput::AdjustSampleRate(u32 sample_rate) { LOG_ERROR(Audio, "AdjustSampleRate unimplemented!"); } -Frontend::Mic::Samples CubebInput::Read() { - Frontend::Mic::Samples samples{}; - Frontend::Mic::Samples queue; +Samples CubebInput::Read() { + Samples samples{}; + Samples queue; while (impl->sample_queue->Pop(queue)) { samples.insert(samples.end(), queue.begin(), queue.end()); } @@ -190,10 +193,4 @@ std::vector ListCubebInputDevices() { 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 f81fb5ac2..1bcb803d5 100644 --- a/src/audio_core/cubeb_input.h +++ b/src/audio_core/cubeb_input.h @@ -5,23 +5,24 @@ #pragma once #include +#include #include -#include "core/frontend/mic.h" +#include "audio_core/input.h" namespace AudioCore { -class CubebInput final : public Frontend::Mic::Interface { +class CubebInput final : public Input { public: explicit CubebInput(std::string device_id); ~CubebInput() override; - void StartSampling(const Frontend::Mic::Parameters& params) override; + void StartSampling(const InputParameters& params) override; void StopSampling() override; void AdjustSampleRate(u32 sample_rate) override; - Frontend::Mic::Samples Read() override; + Samples Read() override; private: struct Impl; @@ -31,11 +32,4 @@ 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/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h index cd967f369..967c0608f 100644 --- a/src/audio_core/cubeb_sink.h +++ b/src/audio_core/cubeb_sink.h @@ -6,6 +6,7 @@ #include #include +#include #include "audio_core/sink.h" namespace AudioCore { diff --git a/src/audio_core/dsp_interface.cpp b/src/audio_core/dsp_interface.cpp index 560ed7a3c..cac30e156 100644 --- a/src/audio_core/dsp_interface.cpp +++ b/src/audio_core/dsp_interface.cpp @@ -16,9 +16,8 @@ namespace AudioCore { DspInterface::DspInterface() = default; DspInterface::~DspInterface() = default; -void DspInterface::SetSink(std::string_view sink_id, std::string_view audio_device) { - sink = CreateSinkFromID(Settings::values.sink_id.GetValue(), - Settings::values.audio_device_id.GetValue()); +void DspInterface::SetSink(AudioCore::SinkType sink_type, std::string_view audio_device) { + sink = CreateSinkFromID(sink_type, audio_device); sink->SetCallback( [this](s16* buffer, std::size_t num_frames) { OutputCallback(buffer, num_frames); }); time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); diff --git a/src/audio_core/dsp_interface.h b/src/audio_core/dsp_interface.h index ba951b5d1..d24d7fc33 100644 --- a/src/audio_core/dsp_interface.h +++ b/src/audio_core/dsp_interface.h @@ -20,6 +20,7 @@ class DSP_DSP; namespace AudioCore { class Sink; +enum class SinkType : u32; class DspInterface { public: @@ -93,8 +94,8 @@ public: /// Unloads the DSP program virtual void UnloadComponent() = 0; - /// Select the sink to use based on sink id. - void SetSink(std::string_view sink_id, std::string_view audio_device); + /// Select the sink to use based on sink type. + void SetSink(SinkType sink_type, std::string_view audio_device); /// Get the current sink Sink& GetSink(); /// Enable/Disable audio stretching. diff --git a/src/audio_core/input.h b/src/audio_core/input.h new file mode 100644 index 000000000..fd3857953 --- /dev/null +++ b/src/audio_core/input.h @@ -0,0 +1,85 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" + +namespace AudioCore { + +enum class Signedness : u8 { + Signed, + Unsigned, +}; + +using Samples = std::vector; + +struct InputParameters { + Signedness sign; + u8 sample_size; + bool buffer_loop; + u32 sample_rate; + u32 buffer_offset; + u32 buffer_size; +}; + +class Input { +public: + Input() = default; + + virtual ~Input() = default; + + /// Starts the microphone. Called by Core + virtual void StartSampling(const InputParameters& params) = 0; + + /// Stops the microphone. Called by Core + virtual void StopSampling() = 0; + + /** + * Called from the actual event timing at a constant period under a given sample rate. + * When sampling is enabled this function is expected to return a buffer of 16 samples in ideal + * conditions, but can be lax if the data is coming in from another source like a real mic. + */ + virtual Samples Read() = 0; + + /** + * Adjusts the Parameters. Implementations should update the parameters field in addition to + * changing the mic to sample according to the new parameters. Called by Core + */ + virtual void AdjustSampleRate(u32 sample_rate) = 0; + + /// Value from 0 - 100 to adjust the mic gain setting. Called by Core + virtual void SetGain(u8 mic_gain) { + gain = mic_gain; + } + + u8 GetGain() const { + return gain; + } + + void SetPower(bool power) { + powered = power; + } + + bool GetPower() const { + return powered; + } + + bool IsSampling() const { + return is_sampling; + } + + const InputParameters& GetParameters() const { + return parameters; + } + +protected: + InputParameters parameters; + u8 gain = 0; + bool is_sampling = false; + bool powered = false; +}; + +} // namespace AudioCore diff --git a/src/audio_core/input_details.cpp b/src/audio_core/input_details.cpp new file mode 100644 index 000000000..b3367b7cc --- /dev/null +++ b/src/audio_core/input_details.cpp @@ -0,0 +1,108 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "audio_core/input_details.h" +#include "audio_core/null_input.h" +#include "audio_core/static_input.h" +#ifdef HAVE_CUBEB +#include "audio_core/cubeb_input.h" +#endif +#ifdef HAVE_OPENAL +#include "audio_core/openal_input.h" +#endif +#include "common/logging/log.h" +#include "core/core.h" + +namespace AudioCore { +namespace { +struct InputDetails { + using FactoryFn = std::unique_ptr (*)(std::string_view); + using ListDevicesFn = std::vector (*)(); + + /// Type of this input. + InputType type; + /// Name for this input. + std::string_view name; + /// A method to call to construct an instance of this type of input. + FactoryFn factory; + /// A method to call to list available devices. + ListDevicesFn list_devices; +}; + +// input_details is ordered in terms of desirability, with the best choice at the top. +constexpr std::array input_details = { +#ifdef HAVE_CUBEB + InputDetails{InputType::Cubeb, "Real Device (Cubeb)", + [](std::string_view device_id) -> std::unique_ptr { + if (!Core::System::GetInstance().HasMicPermission()) { + LOG_WARNING(Audio, + "Microphone permission denied, falling back to null input."); + return std::make_unique(); + } + return std::make_unique(std::string(device_id)); + }, + &ListCubebInputDevices}, +#endif +#ifdef HAVE_OPENAL + InputDetails{InputType::OpenAL, "Real Device (OpenAL)", + [](std::string_view device_id) -> std::unique_ptr { + if (!Core::System::GetInstance().HasMicPermission()) { + LOG_WARNING(Audio, + "Microphone permission denied, falling back to null input."); + return std::make_unique(); + } + return std::make_unique(std::string(device_id)); + }, + &ListOpenALInputDevices}, +#endif + InputDetails{InputType::Static, "Static Noise", + [](std::string_view device_id) -> std::unique_ptr { + return std::make_unique(); + }, + [] { return std::vector{"Static Noise"}; }}, + InputDetails{InputType::Null, "None", + [](std::string_view device_id) -> std::unique_ptr { + return std::make_unique(); + }, + [] { return std::vector{"None"}; }}, +}; + +const InputDetails& GetInputDetails(InputType input_type) { + auto iter = std::find_if( + input_details.begin(), input_details.end(), + [input_type](const auto& input_detail) { return input_detail.type == input_type; }); + + if (input_type == InputType::Auto || iter == input_details.end()) { + if (input_type != InputType::Auto) { + LOG_ERROR(Audio, "AudioCore::GetInputDetails given invalid input_type {}", input_type); + } + // Auto-select. + // input_details is ordered in terms of desirability, with the best choice at the front. + iter = input_details.begin(); + } + + return *iter; +} +} // Anonymous namespace + +std::string_view GetInputName(InputType input_type) { + if (input_type == InputType::Auto) { + return "Auto"; + } + return GetInputDetails(input_type).name; +} + +std::vector GetDeviceListForInput(InputType input_type) { + return GetInputDetails(input_type).list_devices(); +} + +std::unique_ptr CreateInputFromID(InputType input_type, std::string_view device_id) { + return GetInputDetails(input_type).factory(device_id); +} + +} // namespace AudioCore diff --git a/src/audio_core/input_details.h b/src/audio_core/input_details.h new file mode 100644 index 000000000..e5b7b9820 --- /dev/null +++ b/src/audio_core/input_details.h @@ -0,0 +1,36 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "common/common_types.h" + +namespace AudioCore { + +class Input; + +enum class InputType : u32 { + Auto = 0, + Null = 1, + Static = 2, + Cubeb = 3, + OpenAL = 4, + + NumInputTypes, +}; + +/// Gets the name of a input type. +std::string_view GetInputName(InputType input_type); + +/// Gets the list of devices for a particular input identified by the given ID. +std::vector GetDeviceListForInput(InputType input_type); + +/// Creates an audio input identified by the given device ID. +std::unique_ptr CreateInputFromID(InputType input_type, std::string_view device_id); + +} // namespace AudioCore diff --git a/src/audio_core/null_input.h b/src/audio_core/null_input.h new file mode 100644 index 000000000..77cc9a23d --- /dev/null +++ b/src/audio_core/null_input.h @@ -0,0 +1,35 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "audio_core/input.h" +#include "common/swap.h" +#include "common/threadsafe_queue.h" + +namespace AudioCore { + +class NullInput final : public Input { +public: + void StartSampling(const InputParameters& params) override { + parameters = params; + is_sampling = true; + } + + void StopSampling() override { + is_sampling = false; + } + + void AdjustSampleRate(u32 sample_rate) override { + parameters.sample_rate = sample_rate; + } + + Samples Read() override { + return {}; + } +}; + +} // namespace AudioCore diff --git a/src/audio_core/openal_input.cpp b/src/audio_core/openal_input.cpp new file mode 100644 index 000000000..79580c375 --- /dev/null +++ b/src/audio_core/openal_input.cpp @@ -0,0 +1,133 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "audio_core/input.h" +#include "audio_core/openal_input.h" +#include "audio_core/sink.h" +#include "common/logging/log.h" + +namespace AudioCore { + +struct OpenALInput::Impl { + ALCdevice* device = nullptr; + u8 sample_size_in_bytes = 0; +}; + +OpenALInput::OpenALInput(std::string device_id) + : impl(std::make_unique()), device_id(std::move(device_id)) {} + +OpenALInput::~OpenALInput() { + StopSampling(); +} + +void OpenALInput::StartSampling(const InputParameters& params) { + if (is_sampling) { + return; + } + + // OpenAL supports unsigned 8-bit and signed 16-bit PCM. + // TODO: Re-sample the stream. + if ((params.sample_size == 8 && params.sign == Signedness::Signed) || + (params.sample_size == 16 && params.sign == Signedness::Unsigned)) { + LOG_WARNING(Audio, "Application requested unsupported unsigned PCM format. Falling back to " + "supported format."); + } + + parameters = params; + impl->sample_size_in_bytes = params.sample_size / 8; + + auto format = params.sample_size == 16 ? AL_FORMAT_MONO16 : AL_FORMAT_MONO8; + impl->device = alcCaptureOpenDevice( + device_id != auto_device_name && !device_id.empty() ? device_id.c_str() : nullptr, + params.sample_rate, format, static_cast(params.buffer_size)); + if (!impl->device) { + LOG_CRITICAL(Audio, "alcCaptureOpenDevice failed."); + return; + } + + alcCaptureStart(impl->device); + auto error = alcGetError(impl->device); + if (error != ALC_NO_ERROR) { + LOG_CRITICAL(Audio, "alcCaptureStart failed: {}", error); + return; + } + + is_sampling = true; +} + +void OpenALInput::StopSampling() { + if (impl->device) { + alcCaptureStop(impl->device); + alcCaptureCloseDevice(impl->device); + impl->device = nullptr; + } + is_sampling = false; +} + +void OpenALInput::AdjustSampleRate(u32 sample_rate) { + if (!is_sampling) { + return; + } + + auto new_params = parameters; + new_params.sample_rate = sample_rate; + StopSampling(); + StartSampling(new_params); +} + +Samples OpenALInput::Read() { + if (!is_sampling) { + return {}; + } + + ALCint samples_captured = 0; + alcGetIntegerv(impl->device, ALC_CAPTURE_SAMPLES, 1, &samples_captured); + auto error = alcGetError(impl->device); + if (error != ALC_NO_ERROR) { + LOG_WARNING(Audio, "alcGetIntegerv(ALC_CAPTURE_SAMPLES) failed: {}", error); + return {}; + } + + auto num_samples = std::min(samples_captured, static_cast(parameters.buffer_size / + impl->sample_size_in_bytes)); + Samples samples(num_samples * impl->sample_size_in_bytes); + + alcCaptureSamples(impl->device, samples.data(), num_samples); + error = alcGetError(impl->device); + if (error != ALC_NO_ERROR) { + LOG_WARNING(Audio, "alcCaptureSamples failed: {}", error); + return {}; + } + + return samples; +} + +std::vector ListOpenALInputDevices() { + const char* devices_str; + if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT") != AL_FALSE) { + devices_str = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); + } else { + LOG_WARNING( + Audio, + "Missing OpenAL device enumeration extensions, cannot list audio capture devices."); + return {}; + } + + if (!devices_str || *devices_str == '\0') { + return {}; + } + + std::vector device_list; + while (*devices_str != '\0') { + device_list.emplace_back(devices_str); + devices_str += strlen(devices_str) + 1; + } + return device_list; +} + +} // namespace AudioCore diff --git a/src/audio_core/openal_input.h b/src/audio_core/openal_input.h new file mode 100644 index 000000000..8fb2442d8 --- /dev/null +++ b/src/audio_core/openal_input.h @@ -0,0 +1,35 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "audio_core/input.h" + +namespace AudioCore { + +class OpenALInput final : public Input { +public: + explicit OpenALInput(std::string device_id); + ~OpenALInput() override; + + void StartSampling(const InputParameters& params) override; + + void StopSampling() override; + + void AdjustSampleRate(u32 sample_rate) override; + + Samples Read() override; + +private: + struct Impl; + std::unique_ptr impl; + std::string device_id; +}; + +std::vector ListOpenALInputDevices(); + +} // namespace AudioCore diff --git a/src/audio_core/openal_sink.cpp b/src/audio_core/openal_sink.cpp new file mode 100644 index 000000000..c51e300c6 --- /dev/null +++ b/src/audio_core/openal_sink.cpp @@ -0,0 +1,176 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "audio_core/audio_types.h" +#include "audio_core/openal_sink.h" +#include "common/logging/log.h" + +namespace AudioCore { + +struct OpenALSink::Impl { + unsigned int sample_rate = 0; + + ALCdevice* device = nullptr; + ALCcontext* context = nullptr; + ALuint buffer = 0; + ALuint source = 0; + + std::function cb; + + static ALsizei Callback(void* impl_, void* buffer, ALsizei buffer_size_in_bytes); +}; + +OpenALSink::OpenALSink(std::string device_name) : impl(std::make_unique()) { + impl->device = alcOpenDevice( + device_name != auto_device_name && !device_name.empty() ? device_name.c_str() : nullptr); + if (!impl->device) { + LOG_CRITICAL(Audio_Sink, "alcOpenDevice failed."); + return; + } + + impl->context = alcCreateContext(impl->device, nullptr); + if (impl->context == nullptr) { + LOG_CRITICAL(Audio_Sink, "alcCreateContext failed: {}", alcGetError(impl->device)); + alcCloseDevice(impl->device); + return; + } + + if (alcMakeContextCurrent(impl->context) == ALC_FALSE) { + LOG_CRITICAL(Audio_Sink, "alcMakeContextCurrent failed: {}", alcGetError(impl->device)); + alcDestroyContext(impl->context); + alcCloseDevice(impl->device); + return; + } + + if (alIsExtensionPresent("AL_SOFT_callback_buffer") == AL_FALSE) { + if (alGetError() != AL_NO_ERROR) { + LOG_CRITICAL(Audio_Sink, "alIsExtensionPresent failed: {}", alGetError()); + } else { + LOG_CRITICAL(Audio_Sink, "Missing required extension AL_SOFT_callback_buffer."); + } + alcDestroyContext(impl->context); + alcCloseDevice(impl->device); + return; + } + + alGenBuffers(1, &impl->buffer); + if (alGetError() != AL_NO_ERROR) { + LOG_CRITICAL(Audio_Sink, "alGetError failed: {}", alGetError()); + alcDestroyContext(impl->context); + alcCloseDevice(impl->device); + return; + } + + alGenSources(1, &impl->source); + if (alGetError() != AL_NO_ERROR) { + LOG_CRITICAL(Audio_Sink, "alGenSources failed: {}", alGetError()); + alDeleteBuffers(1, &impl->buffer); + alcDestroyContext(impl->context); + alcCloseDevice(impl->device); + return; + } + + auto alBufferCallbackSOFT = + reinterpret_cast(alGetProcAddress("alBufferCallbackSOFT")); + alBufferCallbackSOFT(impl->buffer, AL_FORMAT_STEREO16, native_sample_rate, &Impl::Callback, + impl.get()); + if (alGetError() != AL_NO_ERROR) { + LOG_CRITICAL(Audio_Sink, "alBufferCallbackSOFT failed: {}", alGetError()); + alDeleteSources(1, &impl->source); + alDeleteBuffers(1, &impl->buffer); + alcDestroyContext(impl->context); + alcCloseDevice(impl->device); + return; + } + + alSourcei(impl->source, AL_BUFFER, static_cast(impl->buffer)); + if (alGetError() != AL_NO_ERROR) { + LOG_CRITICAL(Audio_Sink, "alSourcei failed: {}", alGetError()); + alDeleteSources(1, &impl->source); + alDeleteBuffers(1, &impl->buffer); + alcDestroyContext(impl->context); + alcCloseDevice(impl->device); + return; + } + + alSourcePlay(impl->source); + if (alGetError() != AL_NO_ERROR) { + LOG_CRITICAL(Audio_Sink, "alSourcePlay failed: {}", alGetError()); + alDeleteSources(1, &impl->source); + alDeleteBuffers(1, &impl->buffer); + alcDestroyContext(impl->context); + alcCloseDevice(impl->device); + return; + } +} + +OpenALSink::~OpenALSink() { + if (impl->source) { + alSourceStop(impl->source); + alDeleteSources(1, &impl->source); + impl->source = 0; + } + if (impl->buffer) { + alDeleteBuffers(1, &impl->buffer); + impl->buffer = 0; + } + if (impl->context) { + alcDestroyContext(impl->context); + impl->context = nullptr; + } + if (impl->device) { + alcCloseDevice(impl->device); + impl->device = nullptr; + } +} + +unsigned int OpenALSink::GetNativeSampleRate() const { + return native_sample_rate; +} + +void OpenALSink::SetCallback(std::function cb) { + impl->cb = cb; +} + +ALsizei OpenALSink::Impl::Callback(void* impl_, void* buffer, ALsizei buffer_size_in_bytes) { + auto impl = reinterpret_cast(impl_); + if (!impl || !impl->cb) { + return 0; + } + + const size_t num_frames = buffer_size_in_bytes / (2 * sizeof(s16)); + impl->cb(reinterpret_cast(buffer), num_frames); + + return buffer_size_in_bytes; +} + +std::vector ListOpenALSinkDevices() { + const char* devices_str; + if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") != AL_FALSE) { + devices_str = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); + } else if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT") != AL_FALSE) { + devices_str = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); + } else { + LOG_WARNING(Audio_Sink, + "Missing OpenAL device enumeration extensions, cannot list audio devices."); + return {}; + } + + if (!devices_str || *devices_str == '\0') { + return {}; + } + + std::vector device_list; + while (*devices_str != '\0') { + device_list.emplace_back(devices_str); + devices_str += strlen(devices_str) + 1; + } + return device_list; +} + +} // namespace AudioCore diff --git a/src/audio_core/openal_sink.h b/src/audio_core/openal_sink.h new file mode 100644 index 000000000..6adbbac73 --- /dev/null +++ b/src/audio_core/openal_sink.h @@ -0,0 +1,30 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "audio_core/sink.h" + +namespace AudioCore { + +class OpenALSink final : public Sink { +public: + explicit OpenALSink(std::string device_id); + ~OpenALSink() override; + + unsigned int GetNativeSampleRate() const override; + + void SetCallback(std::function cb) override; + +private: + struct Impl; + std::unique_ptr impl; +}; + +std::vector ListOpenALSinkDevices(); + +} // namespace AudioCore diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index 25c71917e..9d547d656 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -14,6 +14,9 @@ #ifdef HAVE_CUBEB #include "audio_core/cubeb_sink.h" #endif +#ifdef HAVE_OPENAL +#include "audio_core/openal_sink.h" +#endif #include "common/logging/log.h" namespace AudioCore { @@ -22,8 +25,10 @@ struct SinkDetails { using FactoryFn = std::unique_ptr (*)(std::string_view); using ListDevicesFn = std::vector (*)(); + /// Type of this sink. + SinkType type; /// Name for this sink. - const char* id; + std::string_view name; /// A method to call to construct an instance of this type of sink. FactoryFn factory; /// A method to call to list available devices. @@ -31,61 +36,66 @@ struct SinkDetails { }; // sink_details is ordered in terms of desirability, with the best choice at the top. -constexpr SinkDetails sink_details[] = { +constexpr std::array sink_details = { #ifdef HAVE_CUBEB - SinkDetails{"cubeb", + SinkDetails{SinkType::Cubeb, "Cubeb", [](std::string_view device_id) -> std::unique_ptr { return std::make_unique(device_id); }, &ListCubebSinkDevices}, #endif +#ifdef HAVE_OPENAL + SinkDetails{SinkType::OpenAL, "OpenAL", + [](std::string_view device_id) -> std::unique_ptr { + return std::make_unique(std::string(device_id)); + }, + &ListOpenALSinkDevices}, +#endif #ifdef HAVE_SDL2 - SinkDetails{"sdl2", + SinkDetails{SinkType::SDL2, "SDL2", [](std::string_view device_id) -> std::unique_ptr { return std::make_unique(std::string(device_id)); }, &ListSDL2SinkDevices}, #endif - SinkDetails{"null", + SinkDetails{SinkType::Null, "None", [](std::string_view device_id) -> std::unique_ptr { return std::make_unique(device_id); }, - [] { return std::vector{"null"}; }}, + [] { return std::vector{"None"}; }}, }; -const SinkDetails& GetSinkDetails(std::string_view sink_id) { - auto iter = - std::find_if(std::begin(sink_details), std::end(sink_details), - [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); +const SinkDetails& GetSinkDetails(SinkType sink_type) { + auto iter = std::find_if( + sink_details.begin(), sink_details.end(), + [sink_type](const auto& sink_detail) { return sink_detail.type == sink_type; }); - if (sink_id == "auto" || iter == std::end(sink_details)) { - if (sink_id != "auto") { - LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id); + if (sink_type == SinkType::Auto || iter == sink_details.end()) { + if (sink_type != SinkType::Auto) { + LOG_ERROR(Audio, "AudioCore::GetSinkDetails given invalid sink_type {}", sink_type); } // Auto-select. // sink_details is ordered in terms of desirability, with the best choice at the front. - iter = std::begin(sink_details); + iter = sink_details.begin(); } return *iter; } } // Anonymous namespace -std::vector GetSinkIDs() { - std::vector sink_ids(std::size(sink_details)); - - std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids), - [](const auto& sink) { return sink.id; }); - - return sink_ids; +std::string_view GetSinkName(SinkType sink_type) { + if (sink_type == SinkType::Auto) { + return "Auto"; + } + return GetSinkDetails(sink_type).name; } -std::vector GetDeviceListForSink(std::string_view sink_id) { - return GetSinkDetails(sink_id).list_devices(); +std::vector GetDeviceListForSink(SinkType sink_type) { + return GetSinkDetails(sink_type).list_devices(); } -std::unique_ptr CreateSinkFromID(std::string_view sink_id, std::string_view device_id) { - return GetSinkDetails(sink_id).factory(device_id); +std::unique_ptr CreateSinkFromID(SinkType sink_type, std::string_view device_id) { + return GetSinkDetails(sink_type).factory(device_id); } } // namespace AudioCore diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h index 28f5d0446..4d0929a6b 100644 --- a/src/audio_core/sink_details.h +++ b/src/audio_core/sink_details.h @@ -4,21 +4,33 @@ #pragma once +#include #include #include #include +#include "common/common_types.h" namespace AudioCore { class Sink; -/// Retrieves the IDs for all available audio sinks. -std::vector GetSinkIDs(); +enum class SinkType : u32 { + Auto = 0, + Null = 1, + Cubeb = 2, + OpenAL = 3, + SDL2 = 4, + + NumSinkTypes, +}; + +/// Gets the name of a sink type. +std::string_view GetSinkName(SinkType sink_type); /// Gets the list of devices for a particular sink identified by the given ID. -std::vector GetDeviceListForSink(std::string_view sink_id); +std::vector GetDeviceListForSink(SinkType sink_type); /// Creates an audio sink identified by the given device ID. -std::unique_ptr CreateSinkFromID(std::string_view sink_id, std::string_view device_id); +std::unique_ptr CreateSinkFromID(SinkType sink_type, std::string_view device_id); } // namespace AudioCore diff --git a/src/audio_core/static_input.cpp b/src/audio_core/static_input.cpp new file mode 100644 index 000000000..61d367820 --- /dev/null +++ b/src/audio_core/static_input.cpp @@ -0,0 +1,42 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "audio_core/input.h" +#include "audio_core/static_input.h" + +namespace AudioCore { + +constexpr std::array NOISE_SAMPLE_8_BIT = {0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF}; + +constexpr std::array NOISE_SAMPLE_16_BIT = { + 0x64, 0x61, 0x74, 0x61, 0x56, 0xD7, 0x00, 0x00, 0x48, 0xF7, 0x86, 0x05, 0x77, 0x1A, 0xF4, 0x1F, + 0x28, 0x0F, 0x6B, 0xEB, 0x1C, 0xC0, 0xCB, 0x9D, 0x46, 0x90, 0xDF, 0x98, 0xEA, 0xAE, 0xB5, 0xC4}; + +StaticInput::StaticInput() + : CACHE_8_BIT{NOISE_SAMPLE_8_BIT.begin(), NOISE_SAMPLE_8_BIT.end()}, + CACHE_16_BIT{NOISE_SAMPLE_16_BIT.begin(), NOISE_SAMPLE_16_BIT.end()} {} + +StaticInput::~StaticInput() = default; + +void StaticInput::StartSampling(const InputParameters& params) { + sample_rate = params.sample_rate; + sample_size = params.sample_size; + + parameters = params; + is_sampling = true; +} + +void StaticInput::StopSampling() { + is_sampling = false; +} + +void StaticInput::AdjustSampleRate(u32 sample_rate) {} + +Samples StaticInput::Read() { + return (sample_size == 8) ? CACHE_8_BIT : CACHE_16_BIT; +} + +} // namespace AudioCore diff --git a/src/audio_core/static_input.h b/src/audio_core/static_input.h new file mode 100644 index 000000000..4c78311bf --- /dev/null +++ b/src/audio_core/static_input.h @@ -0,0 +1,33 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "audio_core/input.h" +#include "common/swap.h" +#include "common/threadsafe_queue.h" + +namespace AudioCore { + +class StaticInput final : public Input { +public: + StaticInput(); + ~StaticInput() override; + + void StartSampling(const InputParameters& params) override; + void StopSampling() override; + void AdjustSampleRate(u32 sample_rate) override; + + Samples Read() override; + +private: + u16 sample_rate = 0; + u8 sample_size = 0; + std::vector CACHE_8_BIT; + std::vector CACHE_16_BIT; +}; + +} // namespace AudioCore diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 12633ba98..6b8332d64 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -15,7 +15,6 @@ #include "common/logging/log.h" #include "common/param_package.h" #include "common/settings.h" -#include "core/frontend/mic.h" #include "core/hle/service/service.h" #include "input_common/main.h" #include "input_common/udp/client.h" @@ -179,12 +178,12 @@ void Config::ReadValues() { // Audio ReadSetting("Audio", Settings::values.audio_emulation); - ReadSetting("Audio", Settings::values.sink_id); ReadSetting("Audio", Settings::values.enable_audio_stretching); - ReadSetting("Audio", Settings::values.audio_device_id); ReadSetting("Audio", Settings::values.volume); - ReadSetting("Audio", Settings::values.mic_input_device); - ReadSetting("Audio", Settings::values.mic_input_type); + ReadSetting("Audio", Settings::values.output_type); + ReadSetting("Audio", Settings::values.output_device); + ReadSetting("Audio", Settings::values.input_type); + ReadSetting("Audio", Settings::values.input_device); // Data Storage ReadSetting("Data Storage", Settings::values.use_virtual_sd); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index e6f020b4d..c958a8a96 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -245,25 +245,32 @@ enable_dsp_lle = # 0 (default): No, 1: Yes enable_dsp_lle_thread = - -# Which audio output engine to use. -# auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available) -output_engine = - # Whether or not to enable the audio-stretching post-processing effect. # This effect adjusts audio speed to match emulation speed and helps prevent audio stutter, # at the cost of increasing audio latency. # 0: No, 1 (default): Yes enable_audio_stretching = -# Which audio device to use. -# auto (default): Auto-select -output_device = - # Output volume. # 1.0 (default): 100%, 0.0; mute volume = +# Which audio output type to use. +# 0 (default): Auto-select, 1: No audio output, 2: Cubeb (if available), 3: OpenAL (if available), 4: SDL2 (if available) +output_type = + +# Which audio output device to use. +# auto (default): Auto-select +output_device = + +# Which audio input type to use. +# 0 (default): Auto-select, 1: No audio input, 2: Static noise, 3: Cubeb (if available), 4: OpenAL (if available) +input_type = + +# Which audio input device to use. +# auto (default): Auto-select +input_device = + [Data Storage] # Whether to create a virtual SD card. # 1 (default): Yes, 0: No diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 09b354b31..c740f5dd7 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -10,7 +10,6 @@ #include "citra_qt/configuration/config.h" #include "common/file_util.h" #include "common/settings.h" -#include "core/frontend/mic.h" #include "core/hle/service/service.h" #include "input_common/main.h" #include "input_common/udp/client.h" @@ -276,10 +275,10 @@ void Config::ReadAudioValues() { ReadGlobalSetting(Settings::values.volume); if (global) { - ReadBasicSetting(Settings::values.sink_id); - ReadBasicSetting(Settings::values.audio_device_id); - ReadBasicSetting(Settings::values.mic_input_device); - ReadBasicSetting(Settings::values.mic_input_type); + ReadBasicSetting(Settings::values.output_type); + ReadBasicSetting(Settings::values.output_device); + ReadBasicSetting(Settings::values.input_type); + ReadBasicSetting(Settings::values.input_device); } qt_config->endGroup(); @@ -847,10 +846,10 @@ void Config::SaveAudioValues() { WriteGlobalSetting(Settings::values.volume); if (global) { - WriteBasicSetting(Settings::values.sink_id); - WriteBasicSetting(Settings::values.audio_device_id); - WriteBasicSetting(Settings::values.mic_input_device); - WriteBasicSetting(Settings::values.mic_input_type); + WriteBasicSetting(Settings::values.output_type); + WriteBasicSetting(Settings::values.output_device); + WriteBasicSetting(Settings::values.input_type); + WriteBasicSetting(Settings::values.input_device); } qt_config->endGroup(); diff --git a/src/citra_qt/configuration/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index af42da8ce..746816a0b 100644 --- a/src/citra_qt/configuration/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -4,32 +4,27 @@ #include #include -#ifdef HAVE_CUBEB -#include "audio_core/cubeb_input.h" -#endif +#include "audio_core/input_details.h" #include "audio_core/sink.h" #include "audio_core/sink_details.h" #include "citra_qt/configuration/configuration_shared.h" #include "citra_qt/configuration/configure_audio.h" #include "common/settings.h" #include "core/core.h" -#include "core/frontend/mic.h" #include "ui_configure_audio.h" #if defined(__APPLE__) #include "citra_qt/macos_authorization.h" #endif -constexpr int DEFAULT_INPUT_DEVICE_INDEX = 0; - ConfigureAudio::ConfigureAudio(QWidget* parent) : QWidget(parent), ui(std::make_unique()) { ui->setupUi(this); - ui->output_sink_combo_box->clear(); - ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); - for (const char* id : AudioCore::GetSinkIDs()) { - ui->output_sink_combo_box->addItem(QString::fromUtf8(id)); + ui->output_type_combo_box->clear(); + for (u32 type = 0; type < static_cast(AudioCore::SinkType::NumSinkTypes); type++) { + ui->output_type_combo_box->addItem(QString::fromUtf8( + AudioCore::GetSinkName(static_cast(type)).data())); } const bool is_running = Core::System::GetInstance().IsPoweredOn(); @@ -38,17 +33,11 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) connect(ui->volume_slider, &QSlider::valueChanged, this, &ConfigureAudio::SetVolumeIndicatorText); - ui->input_device_combo_box->clear(); - ui->input_device_combo_box->addItem(tr("Default")); - -#ifdef HAVE_CUBEB - for (const auto& device : AudioCore::ListCubebInputDevices()) { - ui->input_device_combo_box->addItem(QString::fromStdString(device)); + ui->input_type_combo_box->clear(); + for (u32 type = 0; type < static_cast(AudioCore::InputType::NumInputTypes); type++) { + ui->input_type_combo_box->addItem(QString::fromUtf8( + AudioCore::GetInputName(static_cast(type)).data())); } -#endif - - connect(ui->input_type_combo_box, qOverload(&QComboBox::currentIndexChanged), this, - &ConfigureAudio::UpdateAudioInputDevices); ui->volume_label->setVisible(Settings::IsConfiguringGlobal()); ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal()); @@ -56,18 +45,23 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) SetupPerGameUI(); SetConfiguration(); - connect(ui->output_sink_combo_box, qOverload(&QComboBox::currentIndexChanged), this, + connect(ui->output_type_combo_box, qOverload(&QComboBox::currentIndexChanged), this, &ConfigureAudio::UpdateAudioOutputDevices); + connect(ui->input_type_combo_box, qOverload(&QComboBox::currentIndexChanged), this, + &ConfigureAudio::UpdateAudioInputDevices); } ConfigureAudio::~ConfigureAudio() {} void ConfigureAudio::SetConfiguration() { - SetOutputSinkFromSinkID(); + SetOutputTypeFromSinkType(); + SetInputTypeFromInputType(); // The device list cannot be pre-populated (nor listed) until the output sink is known. - UpdateAudioOutputDevices(ui->output_sink_combo_box->currentIndex()); - SetAudioDeviceFromDeviceID(); + UpdateAudioOutputDevices(ui->output_type_combo_box->currentIndex()); + UpdateAudioInputDevices(ui->input_type_combo_box->currentIndex()); + SetOutputDeviceFromDeviceID(); + SetInputDeviceFromDeviceID(); ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue()); @@ -94,39 +88,44 @@ void ConfigureAudio::SetConfiguration() { s32 selection = static_cast(Settings::values.audio_emulation.GetValue()); ui->emulation_combo_box->setCurrentIndex(selection); } - - s32 index = static_cast(Settings::values.mic_input_type.GetValue()); - ui->input_type_combo_box->setCurrentIndex(index); - - UpdateAudioInputDevices(index); } -void ConfigureAudio::SetOutputSinkFromSinkID() { - int new_sink_index = 0; - - const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue()); - for (int index = 0; index < ui->output_sink_combo_box->count(); index++) { - if (ui->output_sink_combo_box->itemText(index) == sink_id) { - new_sink_index = index; - break; - } - } - - ui->output_sink_combo_box->setCurrentIndex(new_sink_index); +void ConfigureAudio::SetOutputTypeFromSinkType() { + ui->output_type_combo_box->setCurrentIndex( + static_cast(Settings::values.output_type.GetValue())); } -void ConfigureAudio::SetAudioDeviceFromDeviceID() { +void ConfigureAudio::SetOutputDeviceFromDeviceID() { int new_device_index = -1; - const QString device_id = QString::fromStdString(Settings::values.audio_device_id.GetValue()); - for (int index = 0; index < ui->audio_device_combo_box->count(); index++) { - if (ui->audio_device_combo_box->itemText(index) == device_id) { + const QString device_id = QString::fromStdString(Settings::values.output_device.GetValue()); + for (int index = 0; index < ui->output_device_combo_box->count(); index++) { + if (ui->output_device_combo_box->itemText(index) == device_id) { new_device_index = index; break; } } - ui->audio_device_combo_box->setCurrentIndex(new_device_index); + ui->output_device_combo_box->setCurrentIndex(new_device_index); +} + +void ConfigureAudio::SetInputTypeFromInputType() { + ui->input_type_combo_box->setCurrentIndex( + static_cast(Settings::values.input_type.GetValue())); +} + +void ConfigureAudio::SetInputDeviceFromDeviceID() { + int new_device_index = -1; + + const QString device_id = QString::fromStdString(Settings::values.input_device.GetValue()); + for (int index = 0; index < ui->input_device_combo_box->count(); index++) { + if (ui->input_device_combo_box->itemText(index) == device_id) { + new_device_index = index; + break; + } + } + + ui->input_device_combo_box->setCurrentIndex(new_device_index); } void ConfigureAudio::SetVolumeIndicatorText(int percentage) { @@ -144,43 +143,40 @@ void ConfigureAudio::ApplyConfiguration() { }); if (Settings::IsConfiguringGlobal()) { - Settings::values.sink_id = - ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) - .toStdString(); - Settings::values.audio_device_id = - ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) - .toStdString(); - Settings::values.mic_input_type = - static_cast(ui->input_type_combo_box->currentIndex()); - - if (ui->input_device_combo_box->currentIndex() == DEFAULT_INPUT_DEVICE_INDEX) { - Settings::values.mic_input_device = Frontend::Mic::default_device_name; - } else { - Settings::values.mic_input_device = - ui->input_device_combo_box->currentText().toStdString(); - } + Settings::values.output_type = + static_cast(ui->output_type_combo_box->currentIndex()); + Settings::values.output_device = ui->output_device_combo_box->currentText().toStdString(); + Settings::values.input_type = + static_cast(ui->input_type_combo_box->currentIndex()); + Settings::values.input_device = ui->input_device_combo_box->currentText().toStdString(); } } void ConfigureAudio::UpdateAudioOutputDevices(int sink_index) { - ui->audio_device_combo_box->clear(); - ui->audio_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); + auto sink_type = static_cast(sink_index); - const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); - for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) { - ui->audio_device_combo_box->addItem(QString::fromStdString(device)); + ui->output_device_combo_box->clear(); + ui->output_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); + + for (const auto& device : AudioCore::GetDeviceListForSink(sink_type)) { + ui->output_device_combo_box->addItem(QString::fromStdString(device)); } } -void ConfigureAudio::UpdateAudioInputDevices(int index) { +void ConfigureAudio::UpdateAudioInputDevices(int input_index) { + auto input_type = static_cast(input_index); + #if defined(__APPLE__) - if (index == 1) { + if (input_type != AudioCore::InputType::Null && input_type != AudioCore::InputType::Static) { AppleAuthorization::CheckAuthorizationForMicrophone(); } #endif - if (Settings::values.mic_input_device.GetValue() != Frontend::Mic::default_device_name) { - ui->input_device_combo_box->setCurrentText( - QString::fromStdString(Settings::values.mic_input_device.GetValue())); + + ui->input_device_combo_box->clear(); + ui->input_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); + + for (const auto& device : AudioCore::GetDeviceListForInput(input_type)) { + ui->input_device_combo_box->addItem(QString::fromStdString(device)); } } @@ -194,15 +190,15 @@ void ConfigureAudio::SetupPerGameUI() { return; } - ui->output_sink_combo_box->setVisible(false); - ui->output_sink_label->setVisible(false); - ui->audio_device_combo_box->setVisible(false); - ui->audio_device_label->setVisible(false); + ui->output_type_combo_box->setVisible(false); + ui->output_type_label->setVisible(false); + ui->output_device_combo_box->setVisible(false); + ui->output_device_label->setVisible(false); ui->input_type_label->setVisible(false); ui->input_type_combo_box->setVisible(false); ui->input_device_label->setVisible(false); ui->input_device_combo_box->setVisible(false); - ui->microphone_layout->setVisible(false); + ui->input_layout->setVisible(false); connect(ui->volume_combo_box, qOverload(&QComboBox::activated), this, [this](int index) { ui->volume_slider->setEnabled(index == 1); diff --git a/src/citra_qt/configuration/configure_audio.h b/src/citra_qt/configuration/configure_audio.h index 514c7945c..a25153c50 100644 --- a/src/citra_qt/configuration/configure_audio.h +++ b/src/citra_qt/configuration/configure_audio.h @@ -30,8 +30,10 @@ private: void UpdateAudioOutputDevices(int sink_index); void UpdateAudioInputDevices(int index); - void SetOutputSinkFromSinkID(); - void SetAudioDeviceFromDeviceID(); + void SetOutputTypeFromSinkType(); + void SetOutputDeviceFromDeviceID(); + void SetInputTypeFromInputType(); + void SetInputDeviceFromDeviceID(); void SetVolumeIndicatorText(int percentage); void SetupPerGameUI(); diff --git a/src/citra_qt/configuration/configure_audio.ui b/src/citra_qt/configuration/configure_audio.ui index 3a1a28dd3..63df78c5a 100644 --- a/src/citra_qt/configuration/configure_audio.ui +++ b/src/citra_qt/configuration/configure_audio.ui @@ -14,7 +14,7 @@ - Audio + Output @@ -62,16 +62,26 @@ - - - + + + - Output Engine + Output Type - - + + + + + + + Output Device + + + + + @@ -85,20 +95,6 @@ - - - - - - Audio Device - - - - - - - - @@ -190,54 +186,30 @@ - + Microphone - - - - - - - Input Type - - - - - - - - None - - - - - Real Device - - - - - Static Noise - - - - - + + + + + Input Type + + - - - - - - Input Device - - - - - - - + + + + + + + Input Device + + + + + @@ -259,10 +231,10 @@ emulation_combo_box - output_sink_combo_box toggle_audio_stretching - audio_device_combo_box volume_slider + output_type_combo_box + output_device_combo_box input_type_combo_box input_device_combo_box diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 1617968cc..d7130d0f3 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -101,6 +101,10 @@ #include "video_core/renderer_base.h" #include "video_core/video_core.h" +#ifdef __APPLE__ +#include "macos_authorization.h" +#endif + #ifdef USE_DISCORD_PRESENCE #include "citra_qt/discord_impl.h" #endif @@ -2779,6 +2783,11 @@ int main(int argc, char* argv[]) { // Register Qt image interface system.RegisterImageInterface(std::make_shared()); +#ifdef __APPLE__ + // Register microphone permission check. + system.RegisterMicPermissionCheck(&AppleAuthorization::CheckAuthorizationForMicrophone); +#endif + main_window.show(); QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window, diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 616765edb..6456bde75 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -89,7 +89,7 @@ void Apply() { auto& system = Core::System::GetInstance(); if (system.IsPoweredOn()) { system.CoreTiming().UpdateClockSpeed(values.cpu_clock_percentage.GetValue()); - Core::DSP().SetSink(values.sink_id.GetValue(), values.audio_device_id.GetValue()); + Core::DSP().SetSink(values.output_type.GetValue(), values.output_device.GetValue()); Core::DSP().EnableStretching(values.enable_audio_stretching.GetValue()); auto hid = Service::HID::GetModule(system); @@ -156,11 +156,11 @@ void LogSettings() { log_setting("Utility_CustomTextures", values.custom_textures.GetValue()); log_setting("Utility_UseDiskShaderCache", values.use_disk_shader_cache.GetValue()); log_setting("Audio_Emulation", GetAudioEmulationName(values.audio_emulation.GetValue())); - log_setting("Audio_OutputEngine", values.sink_id.GetValue()); + log_setting("Audio_OutputType", values.output_type.GetValue()); + log_setting("Audio_OutputDevice", values.output_device.GetValue()); + log_setting("Audio_InputType", values.input_type.GetValue()); + log_setting("Audio_InputDevice", values.input_device.GetValue()); log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue()); - log_setting("Audio_OutputDevice", values.audio_device_id.GetValue()); - log_setting("Audio_InputDeviceType", values.mic_input_type.GetValue()); - log_setting("Audio_InputDevice", values.mic_input_device.GetValue()); using namespace Service::CAM; log_setting("Camera_OuterRightName", values.camera_name[OuterRightCamera]); log_setting("Camera_OuterRightConfig", values.camera_config[OuterRightCamera]); diff --git a/src/common/settings.h b/src/common/settings.h index 14cd53734..1aa82426c 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -10,6 +10,8 @@ #include #include #include +#include "audio_core/input_details.h" +#include "audio_core/sink_details.h" #include "common/common_types.h" #include "core/hle/service/cam/cam_params.h" @@ -42,12 +44,6 @@ enum class LayoutOption : u32 { MobileLandscape, }; -enum class MicInputType : u32 { - None = 0, - Real = 1, - Static = 2, -}; - enum class StereoRenderOption : u32 { Off = 0, SideBySide = 1, @@ -482,12 +478,12 @@ struct Values { // Audio bool audio_muted; SwitchableSetting audio_emulation{AudioEmulation::HLE, "audio_emulation"}; - Setting sink_id{"auto", "output_engine"}; SwitchableSetting enable_audio_stretching{true, "enable_audio_stretching"}; - Setting audio_device_id{"auto", "output_device"}; SwitchableSetting volume{1.f, 0.f, 1.f, "volume"}; - Setting mic_input_type{MicInputType::None, "mic_input_type"}; - Setting mic_input_device{"Default", "mic_input_device"}; + Setting output_type{AudioCore::SinkType::Auto, "output_type"}; + Setting output_device{"auto", "output_device"}; + Setting input_type{AudioCore::InputType::Auto, "input_type"}; + Setting input_device{"auto", "input_device"}; // Camera std::array camera_name; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 14e886215..7863e8ba7 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -110,8 +110,6 @@ add_library(citra_core STATIC frontend/image_interface.cpp frontend/image_interface.h frontend/input.h - frontend/mic.cpp - frontend/mic.h gdbstub/gdbstub.cpp gdbstub/gdbstub.h gdbstub/hio.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index 5de35b47c..4dddfb5e1 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -411,8 +411,8 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, memory->SetDSP(*dsp_core); - dsp_core->SetSink(Settings::values.sink_id.GetValue(), - Settings::values.audio_device_id.GetValue()); + dsp_core->SetSink(Settings::values.output_type.GetValue(), + Settings::values.output_device.GetValue()); dsp_core->EnableStretching(Settings::values.enable_audio_stretching.GetValue()); telemetry_session = std::make_unique(); diff --git a/src/core/core.h b/src/core/core.h index 4468f709c..d4cab87e3 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -304,6 +304,17 @@ public: return registered_image_interface; } + /// Function for checking OS microphone permissions. + + void RegisterMicPermissionCheck(const std::function& permission_func) { + mic_permission_func = permission_func; + } + + [[nodiscard]] bool HasMicPermission() { + return !mic_permission_func || mic_permission_granted || + (mic_permission_granted = mic_permission_func()); + } + void SaveState(u32 slot) const; void LoadState(u32 slot); @@ -397,6 +408,9 @@ private: Signal current_signal; u32 signal_param; + std::function mic_permission_func; + bool mic_permission_granted = false; + friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int file_version); diff --git a/src/core/frontend/mic.cpp b/src/core/frontend/mic.cpp deleted file mode 100644 index c9dbc9cc7..000000000 --- a/src/core/frontend/mic.cpp +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2019 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#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, - 0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF}; - -constexpr std::array NOISE_SAMPLE_16_BIT = { - 0x64, 0x61, 0x74, 0x61, 0x56, 0xD7, 0x00, 0x00, 0x48, 0xF7, 0x86, 0x05, 0x77, 0x1A, 0xF4, 0x1F, - 0x28, 0x0F, 0x6B, 0xEB, 0x1C, 0xC0, 0xCB, 0x9D, 0x46, 0x90, 0xDF, 0x98, 0xEA, 0xAE, 0xB5, 0xC4}; - -Interface::~Interface() = default; - -void NullMic::StartSampling(const Parameters& params) { - parameters = params; - is_sampling = true; -} - -void NullMic::StopSampling() { - is_sampling = false; -} - -void NullMic::AdjustSampleRate(u32 sample_rate) { - parameters.sample_rate = sample_rate; -} - -Samples NullMic::Read() { - return {}; -} - -StaticMic::StaticMic() - : CACHE_8_BIT{NOISE_SAMPLE_8_BIT.begin(), NOISE_SAMPLE_8_BIT.end()}, - CACHE_16_BIT{NOISE_SAMPLE_16_BIT.begin(), NOISE_SAMPLE_16_BIT.end()} {} - -StaticMic::~StaticMic() = default; - -void StaticMic::StartSampling(const Parameters& params) { - sample_rate = params.sample_rate; - sample_size = params.sample_size; - - parameters = params; - is_sampling = true; -} - -void StaticMic::StopSampling() { - is_sampling = false; -} - -void StaticMic::AdjustSampleRate(u32 sample_rate) {} - -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 deleted file mode 100644 index 28738df52..000000000 --- a/src/core/frontend/mic.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2019 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include "common/swap.h" -#include "common/threadsafe_queue.h" - -namespace Frontend::Mic { - -constexpr char default_device_name[] = "Default"; - -enum class Signedness : u8 { - Signed, - Unsigned, -}; - -using Samples = std::vector; - -struct Parameters { - Signedness sign; - u8 sample_size; - bool buffer_loop; - u32 sample_rate; - u32 buffer_offset; - u32 buffer_size; -}; - -class Interface { -public: - Interface() = default; - - virtual ~Interface(); - - /// Starts the microphone. Called by Core - virtual void StartSampling(const Parameters& params) = 0; - - /// Stops the microphone. Called by Core - virtual void StopSampling() = 0; - - /** - * Called from the actual event timing at a constant period under a given sample rate. - * When sampling is enabled this function is expected to return a buffer of 16 samples in ideal - * conditions, but can be lax if the data is coming in from another source like a real mic. - */ - virtual Samples Read() = 0; - - /** - * Adjusts the Parameters. Implementations should update the parameters field in addition to - * changing the mic to sample according to the new parameters. Called by Core - */ - virtual void AdjustSampleRate(u32 sample_rate) = 0; - - /// Value from 0 - 100 to adjust the mic gain setting. Called by Core - virtual void SetGain(u8 mic_gain) { - gain = mic_gain; - } - - u8 GetGain() const { - return gain; - } - - void SetPower(bool power) { - powered = power; - } - - bool GetPower() const { - return powered; - } - - bool IsSampling() const { - return is_sampling; - } - - const Parameters& GetParameters() const { - return parameters; - } - -protected: - Parameters parameters; - u8 gain = 0; - bool is_sampling = false; - bool powered = false; -}; - -class NullMic final : public Interface { -public: - void StartSampling(const Parameters& params) override; - - void StopSampling() override; - - void AdjustSampleRate(u32 sample_rate) override; - - Samples Read() override; -}; - -class StaticMic final : public Interface { -public: - StaticMic(); - ~StaticMic() override; - - void StartSampling(const Parameters& params) override; - void StopSampling() override; - void AdjustSampleRate(u32 sample_rate) override; - - Samples Read() override; - -private: - u16 sample_rate = 0; - u8 sample_size = 0; - std::vector CACHE_8_BIT; - 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 afae803e7..ba229ae0a 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -3,11 +3,12 @@ // Refer to the license.txt file included. #include +#include "audio_core/input.h" +#include "audio_core/input_details.h" #include "common/archives.h" #include "common/logging/log.h" #include "common/settings.h" #include "core/core.h" -#include "core/frontend/mic.h" #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" @@ -167,7 +168,7 @@ struct MIC_U::Impl { return; } - Frontend::Mic::Samples samples = mic->Read(); + AudioCore::Samples samples = mic->Read(); if (!samples.empty()) { // write the samples to sharedmem page state.WriteSamples(samples); @@ -180,8 +181,8 @@ struct MIC_U::Impl { void StartSampling() { auto sign = encoding == Encoding::PCM8Signed || encoding == Encoding::PCM16Signed - ? Frontend::Mic::Signedness::Signed - : Frontend::Mic::Signedness::Unsigned; + ? AudioCore::Signedness::Signed + : AudioCore::Signedness::Unsigned; mic->StartSampling({sign, state.sample_size, state.looped_buffer, GetSampleRateInHz(state.sample_rate), state.initial_offset, static_cast(state.size)}); @@ -349,21 +350,9 @@ struct MIC_U::Impl { } void CreateMic() { - std::unique_ptr new_mic; - switch (Settings::values.mic_input_type.GetValue()) { - case Settings::MicInputType::None: - new_mic = std::make_unique(); - break; - case Settings::MicInputType::Real: - new_mic = Frontend::Mic::CreateRealMic(Settings::values.mic_input_device.GetValue()); - break; - case Settings::MicInputType::Static: - new_mic = std::make_unique(); - break; - default: - LOG_CRITICAL(Audio, "Mic type not found. Defaulting to null mic"); - new_mic = std::make_unique(); - } + std::unique_ptr new_mic = AudioCore::CreateInputFromID( + Settings::values.input_type.GetValue(), Settings::values.input_device.GetValue()); + // If theres already a mic, copy over any data to the new mic impl if (mic) { new_mic->SetGain(mic->GetGain()); @@ -386,7 +375,7 @@ struct MIC_U::Impl { u32 client_version = 0; bool allow_shell_closed = false; bool clamp = false; - std::unique_ptr mic; + std::unique_ptr mic; Core::Timing& timing; State state{}; Encoding encoding{}; diff --git a/src/core/hle/service/plgldr/plgldr.cpp b/src/core/hle/service/plgldr/plgldr.cpp index d8a563c67..41cb33b97 100644 --- a/src/core/hle/service/plgldr/plgldr.cpp +++ b/src/core/hle/service/plgldr/plgldr.cpp @@ -26,7 +26,6 @@ #include "common/settings.h" #include "core/core.h" #include "core/file_sys/plugin_3gx.h" -#include "core/frontend/mic.h" #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 2ae8863ae..88f493196 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -124,7 +124,8 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { Telemetry::AppendOSInfo(field_collection); // Log user configuration information - AddField(Telemetry::FieldType::UserConfig, "Audio_SinkId", Settings::values.sink_id.GetValue()); + AddField(Telemetry::FieldType::UserConfig, "Audio_SinkId", + static_cast(Settings::values.output_type.GetValue())); AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching", Settings::values.enable_audio_stretching.GetValue()); AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit",