mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	audio_core: Implement OpenAL backend (#6450)
This commit is contained in:
		
							parent
							
								
									ce553ab995
								
							
						
					
					
						commit
						055a58f01e
					
				
					 48 changed files with 1042 additions and 576 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							|  | @ -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 | ||||
|  |  | |||
|  | @ -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) | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										13
									
								
								externals/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								externals/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							|  | @ -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() | ||||
|  |  | |||
							
								
								
									
										1
									
								
								externals/openal-soft
									
										
									
									
										vendored
									
									
										Submodule
									
								
							
							
						
						
									
										1
									
								
								externals/openal-soft
									
										
									
									
										vendored
									
									
										Submodule
									
								
							|  | @ -0,0 +1 @@ | |||
| Subproject commit d9fed51aa6391debc31dbbca550f055c980afe70 | ||||
|  | @ -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<SettingsItem> sl) { | ||||
|  |  | |||
|  | @ -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"; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -1,38 +0,0 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <jni.h> | ||||
| #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<Frontend::Mic::Interface> 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<AudioCore::CubebInput>(std::move(mic_device_name)); | ||||
|     } else { | ||||
|         LOG_WARNING(Frontend, "Mic permissions denied"); | ||||
|         return std::make_unique<Frontend::Mic::NullMic>(); | ||||
|     } | ||||
| #else | ||||
|     LOG_WARNING(Frontend, "No cubeb support"); | ||||
|     return std::make_unique<Frontend::Mic::NullMic>(); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| } // namespace Mic
 | ||||
|  | @ -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<Frontend::Mic::Interface> Create(std::string mic_device_name) override; | ||||
| 
 | ||||
| private: | ||||
|     bool permission_granted = false; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Mic
 | ||||
|  | @ -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<std::mutex> lock(running_mutex); | ||||
|  | @ -183,8 +186,8 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { | |||
|     system.RegisterMiiSelector(std::make_shared<MiiSelector::AndroidMiiSelector>()); | ||||
|     system.RegisterSoftwareKeyboard(std::make_shared<SoftwareKeyboard::AndroidKeyboard>()); | ||||
| 
 | ||||
|     // Register real Mic factory
 | ||||
|     Frontend::Mic::RegisterRealMicFactory(std::make_unique<Mic::AndroidFactory>()); | ||||
|     // Register microphone permission check
 | ||||
|     Core::System::GetInstance().RegisterMicPermissionCheck(&CheckMicPermission); | ||||
| 
 | ||||
|     InputManager::Init(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -143,15 +143,19 @@ | |||
|     </integer-array> | ||||
| 
 | ||||
|     <string-array name="audioInputTypeNames"> | ||||
|         <item>Auto</item> | ||||
|         <item>None</item> | ||||
|         <item>Real Device</item> | ||||
|         <item>Static Noise</item> | ||||
|         <item>Real Device (Cubeb)</item> | ||||
|         <item>Real Device (OpenAL)</item> | ||||
|     </string-array> | ||||
| 
 | ||||
|     <integer-array name="audioInputTypeValues"> | ||||
|         <item>0</item> | ||||
|         <item>1</item> | ||||
|         <item>2</item> | ||||
|         <item>3</item> | ||||
|         <item>4</item> | ||||
|     </integer-array> | ||||
| 
 | ||||
|     <string-array name="render3dModes"> | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|     $<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.h> | ||||
|     $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h cubeb_input.cpp cubeb_input.h> | ||||
|     $<$<BOOL:${ENABLE_OPENAL}>: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() | ||||
|  |  | |||
|  | @ -6,11 +6,14 @@ | |||
| #include <vector> | ||||
| #include <cubeb/cubeb.h> | ||||
| #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<Frontend::Mic::Samples>; | ||||
| using SampleQueue = Common::SPSCQueue<Samples>; | ||||
| 
 | ||||
| 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<std::string> ListCubebInputDevices() { | |||
|     return device_list; | ||||
| } | ||||
| 
 | ||||
| CubebFactory::~CubebFactory() = default; | ||||
| 
 | ||||
| std::unique_ptr<Frontend::Mic::Interface> CubebFactory::Create(std::string mic_device_name) { | ||||
|     return std::make_unique<CubebInput>(std::move(mic_device_name)); | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -5,23 +5,24 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #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<std::string> ListCubebInputDevices(); | ||||
| 
 | ||||
| class CubebFactory final : public Frontend::Mic::RealMicFactory { | ||||
| public: | ||||
|     ~CubebFactory() override; | ||||
| 
 | ||||
|     std::unique_ptr<Frontend::Mic::Interface> Create(std::string mic_device_name) override; | ||||
| }; | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| 
 | ||||
| #include <cstddef> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include "audio_core/sink.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
|  |  | |||
|  | @ -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()); | ||||
|  |  | |||
|  | @ -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.
 | ||||
|  |  | |||
							
								
								
									
										85
									
								
								src/audio_core/input.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/audio_core/input.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <vector> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| enum class Signedness : u8 { | ||||
|     Signed, | ||||
|     Unsigned, | ||||
| }; | ||||
| 
 | ||||
| using Samples = std::vector<u8>; | ||||
| 
 | ||||
| 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
 | ||||
							
								
								
									
										108
									
								
								src/audio_core/input_details.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/audio_core/input_details.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,108 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #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<Input> (*)(std::string_view); | ||||
|     using ListDevicesFn = std::vector<std::string> (*)(); | ||||
| 
 | ||||
|     /// 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<Input> { | ||||
|                      if (!Core::System::GetInstance().HasMicPermission()) { | ||||
|                          LOG_WARNING(Audio, | ||||
|                                      "Microphone permission denied, falling back to null input."); | ||||
|                          return std::make_unique<NullInput>(); | ||||
|                      } | ||||
|                      return std::make_unique<CubebInput>(std::string(device_id)); | ||||
|                  }, | ||||
|                  &ListCubebInputDevices}, | ||||
| #endif | ||||
| #ifdef HAVE_OPENAL | ||||
|     InputDetails{InputType::OpenAL, "Real Device (OpenAL)", | ||||
|                  [](std::string_view device_id) -> std::unique_ptr<Input> { | ||||
|                      if (!Core::System::GetInstance().HasMicPermission()) { | ||||
|                          LOG_WARNING(Audio, | ||||
|                                      "Microphone permission denied, falling back to null input."); | ||||
|                          return std::make_unique<NullInput>(); | ||||
|                      } | ||||
|                      return std::make_unique<OpenALInput>(std::string(device_id)); | ||||
|                  }, | ||||
|                  &ListOpenALInputDevices}, | ||||
| #endif | ||||
|     InputDetails{InputType::Static, "Static Noise", | ||||
|                  [](std::string_view device_id) -> std::unique_ptr<Input> { | ||||
|                      return std::make_unique<StaticInput>(); | ||||
|                  }, | ||||
|                  [] { return std::vector<std::string>{"Static Noise"}; }}, | ||||
|     InputDetails{InputType::Null, "None", | ||||
|                  [](std::string_view device_id) -> std::unique_ptr<Input> { | ||||
|                      return std::make_unique<NullInput>(); | ||||
|                  }, | ||||
|                  [] { return std::vector<std::string>{"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<std::string> GetDeviceListForInput(InputType input_type) { | ||||
|     return GetInputDetails(input_type).list_devices(); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<Input> CreateInputFromID(InputType input_type, std::string_view device_id) { | ||||
|     return GetInputDetails(input_type).factory(device_id); | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										36
									
								
								src/audio_core/input_details.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/audio_core/input_details.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <memory> | ||||
| #include <string> | ||||
| #include <string_view> | ||||
| #include <vector> | ||||
| #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<std::string> GetDeviceListForInput(InputType input_type); | ||||
| 
 | ||||
| /// Creates an audio input identified by the given device ID.
 | ||||
| std::unique_ptr<Input> CreateInputFromID(InputType input_type, std::string_view device_id); | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										35
									
								
								src/audio_core/null_input.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/audio_core/null_input.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <memory> | ||||
| #include <vector> | ||||
| #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
 | ||||
							
								
								
									
										133
									
								
								src/audio_core/openal_input.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/audio_core/openal_input.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,133 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| #include <AL/al.h> | ||||
| #include <AL/alc.h> | ||||
| #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<Impl>()), 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<ALsizei>(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<ALsizei>(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<std::string> 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<std::string> device_list; | ||||
|     while (*devices_str != '\0') { | ||||
|         device_list.emplace_back(devices_str); | ||||
|         devices_str += strlen(devices_str) + 1; | ||||
|     } | ||||
|     return device_list; | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										35
									
								
								src/audio_core/openal_input.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/audio_core/openal_input.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #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> impl; | ||||
|     std::string device_id; | ||||
| }; | ||||
| 
 | ||||
| std::vector<std::string> ListOpenALInputDevices(); | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										176
									
								
								src/audio_core/openal_sink.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								src/audio_core/openal_sink.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,176 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <AL/al.h> | ||||
| #include <AL/alc.h> | ||||
| #include <AL/alext.h> | ||||
| #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<void(s16*, std::size_t)> cb; | ||||
| 
 | ||||
|     static ALsizei Callback(void* impl_, void* buffer, ALsizei buffer_size_in_bytes); | ||||
| }; | ||||
| 
 | ||||
| OpenALSink::OpenALSink(std::string device_name) : impl(std::make_unique<Impl>()) { | ||||
|     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<LPALBUFFERCALLBACKSOFT>(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<ALint>(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<void(s16*, std::size_t)> cb) { | ||||
|     impl->cb = cb; | ||||
| } | ||||
| 
 | ||||
| ALsizei OpenALSink::Impl::Callback(void* impl_, void* buffer, ALsizei buffer_size_in_bytes) { | ||||
|     auto impl = reinterpret_cast<Impl*>(impl_); | ||||
|     if (!impl || !impl->cb) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     const size_t num_frames = buffer_size_in_bytes / (2 * sizeof(s16)); | ||||
|     impl->cb(reinterpret_cast<s16*>(buffer), num_frames); | ||||
| 
 | ||||
|     return buffer_size_in_bytes; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> 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<std::string> device_list; | ||||
|     while (*devices_str != '\0') { | ||||
|         device_list.emplace_back(devices_str); | ||||
|         devices_str += strlen(devices_str) + 1; | ||||
|     } | ||||
|     return device_list; | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										30
									
								
								src/audio_core/openal_sink.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/audio_core/openal_sink.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <cstddef> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #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<void(s16*, std::size_t)> cb) override; | ||||
| 
 | ||||
| private: | ||||
|     struct Impl; | ||||
|     std::unique_ptr<Impl> impl; | ||||
| }; | ||||
| 
 | ||||
| std::vector<std::string> ListOpenALSinkDevices(); | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  | @ -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<Sink> (*)(std::string_view); | ||||
|     using ListDevicesFn = std::vector<std::string> (*)(); | ||||
| 
 | ||||
|     /// 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<Sink> { | ||||
|                     return std::make_unique<CubebSink>(device_id); | ||||
|                 }, | ||||
|                 &ListCubebSinkDevices}, | ||||
| #endif | ||||
| #ifdef HAVE_OPENAL | ||||
|     SinkDetails{SinkType::OpenAL, "OpenAL", | ||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||
|                     return std::make_unique<OpenALSink>(std::string(device_id)); | ||||
|                 }, | ||||
|                 &ListOpenALSinkDevices}, | ||||
| #endif | ||||
| #ifdef HAVE_SDL2 | ||||
|     SinkDetails{"sdl2", | ||||
|     SinkDetails{SinkType::SDL2, "SDL2", | ||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||
|                     return std::make_unique<SDL2Sink>(std::string(device_id)); | ||||
|                 }, | ||||
|                 &ListSDL2SinkDevices}, | ||||
| #endif | ||||
|     SinkDetails{"null", | ||||
|     SinkDetails{SinkType::Null, "None", | ||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||
|                     return std::make_unique<NullSink>(device_id); | ||||
|                 }, | ||||
|                 [] { return std::vector<std::string>{"null"}; }}, | ||||
|                 [] { return std::vector<std::string>{"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<const char*> GetSinkIDs() { | ||||
|     std::vector<const char*> 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<std::string> GetDeviceListForSink(std::string_view sink_id) { | ||||
|     return GetSinkDetails(sink_id).list_devices(); | ||||
| std::vector<std::string> GetDeviceListForSink(SinkType sink_type) { | ||||
|     return GetSinkDetails(sink_type).list_devices(); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) { | ||||
|     return GetSinkDetails(sink_id).factory(device_id); | ||||
| std::unique_ptr<Sink> CreateSinkFromID(SinkType sink_type, std::string_view device_id) { | ||||
|     return GetSinkDetails(sink_type).factory(device_id); | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -4,21 +4,33 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <string_view> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| class Sink; | ||||
| 
 | ||||
| /// Retrieves the IDs for all available audio sinks.
 | ||||
| std::vector<const char*> 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<std::string> GetDeviceListForSink(std::string_view sink_id); | ||||
| std::vector<std::string> GetDeviceListForSink(SinkType sink_type); | ||||
| 
 | ||||
| /// Creates an audio sink identified by the given device ID.
 | ||||
| std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id); | ||||
| std::unique_ptr<Sink> CreateSinkFromID(SinkType sink_type, std::string_view device_id); | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
							
								
								
									
										42
									
								
								src/audio_core/static_input.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/audio_core/static_input.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| // Copyright 2019 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <array> | ||||
| #include "audio_core/input.h" | ||||
| #include "audio_core/static_input.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| constexpr std::array<u8, 16> NOISE_SAMPLE_8_BIT = {0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|                                                    0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF}; | ||||
| 
 | ||||
| constexpr std::array<u8, 32> 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
 | ||||
							
								
								
									
										33
									
								
								src/audio_core/static_input.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/audio_core/static_input.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <memory> | ||||
| #include <vector> | ||||
| #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<u8> CACHE_8_BIT; | ||||
|     std::vector<u8> CACHE_16_BIT; | ||||
| }; | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -4,32 +4,27 @@ | |||
| 
 | ||||
| #include <memory> | ||||
| #include <QtGlobal> | ||||
| #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::ConfigureAudio>()) { | ||||
|     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<u32>(AudioCore::SinkType::NumSinkTypes); type++) { | ||||
|         ui->output_type_combo_box->addItem(QString::fromUtf8( | ||||
|             AudioCore::GetSinkName(static_cast<AudioCore::SinkType>(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<u32>(AudioCore::InputType::NumInputTypes); type++) { | ||||
|         ui->input_type_combo_box->addItem(QString::fromUtf8( | ||||
|             AudioCore::GetInputName(static_cast<AudioCore::InputType>(type)).data())); | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     connect(ui->input_type_combo_box, qOverload<int>(&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<int>(&QComboBox::currentIndexChanged), this, | ||||
|     connect(ui->output_type_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, | ||||
|             &ConfigureAudio::UpdateAudioOutputDevices); | ||||
|     connect(ui->input_type_combo_box, qOverload<int>(&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<s32>(Settings::values.audio_emulation.GetValue()); | ||||
|         ui->emulation_combo_box->setCurrentIndex(selection); | ||||
|     } | ||||
| 
 | ||||
|     s32 index = static_cast<s32>(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<int>(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<int>(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<Settings::MicInputType>(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<AudioCore::SinkType>(ui->output_type_combo_box->currentIndex()); | ||||
|         Settings::values.output_device = ui->output_device_combo_box->currentText().toStdString(); | ||||
|         Settings::values.input_type = | ||||
|             static_cast<AudioCore::InputType>(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<AudioCore::SinkType>(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<AudioCore::InputType>(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<int>(&QComboBox::activated), this, [this](int index) { | ||||
|         ui->volume_slider->setEnabled(index == 1); | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ | |||
|    <item> | ||||
|     <widget class="QGroupBox" name="groupBox"> | ||||
|      <property name="title"> | ||||
|       <string>Audio</string> | ||||
|       <string>Output</string> | ||||
|      </property> | ||||
|      <layout class="QVBoxLayout"> | ||||
|       <item> | ||||
|  | @ -62,16 +62,26 @@ | |||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <layout class="QHBoxLayout" name="output_engine_layout"> | ||||
|         <item> | ||||
|          <widget class="QLabel" name="output_sink_label"> | ||||
|        <layout class="QGridLayout" name="output_layout" columnstretch="1,1"> | ||||
|         <item row="0" column="0"> | ||||
|          <widget class="QLabel" name="output_type_label"> | ||||
|           <property name="text"> | ||||
|            <string>Output Engine</string> | ||||
|            <string>Output Type</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item> | ||||
|          <widget class="QComboBox" name="output_sink_combo_box"/> | ||||
|         <item row="0" column="1"> | ||||
|          <widget class="QComboBox" name="output_type_combo_box"/> | ||||
|         </item> | ||||
|         <item row="1" column="0"> | ||||
|          <widget class="QLabel" name="output_device_label"> | ||||
|           <property name="text"> | ||||
|            <string>Output Device</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="1" column="1"> | ||||
|          <widget class="QComboBox" name="output_device_combo_box"/> | ||||
|         </item> | ||||
|        </layout> | ||||
|       </item> | ||||
|  | @ -85,20 +95,6 @@ | |||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <layout class="QHBoxLayout" name="audio_device_layout"> | ||||
|         <item> | ||||
|          <widget class="QLabel" name="audio_device_label"> | ||||
|           <property name="text"> | ||||
|            <string>Audio Device</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item> | ||||
|          <widget class="QComboBox" name="audio_device_combo_box"/> | ||||
|         </item> | ||||
|        </layout> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QWidget" name="volume_layout" native="true"> | ||||
|         <layout class="QHBoxLayout" name="horizontalLayout_2"> | ||||
|  | @ -190,54 +186,30 @@ | |||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QGroupBox" name="microphone_layout"> | ||||
|     <widget class="QGroupBox" name="input_layout"> | ||||
|      <property name="title"> | ||||
|       <string>Microphone</string> | ||||
|      </property> | ||||
|      <layout class="QVBoxLayout" name="verticalLayout"> | ||||
|       <item> | ||||
|        <layout class="QHBoxLayout" name="horizontalLayout"> | ||||
|         <item> | ||||
|          <widget class="QLabel" name="input_type_label"> | ||||
|           <property name="text"> | ||||
|            <string>Input Type</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item> | ||||
|          <widget class="QComboBox" name="input_type_combo_box"> | ||||
|           <item> | ||||
|            <property name="text"> | ||||
|             <string>None</string> | ||||
|            </property> | ||||
|           </item> | ||||
|           <item> | ||||
|            <property name="text"> | ||||
|             <string>Real Device</string> | ||||
|            </property> | ||||
|           </item> | ||||
|           <item> | ||||
|            <property name="text"> | ||||
|             <string>Static Noise</string> | ||||
|            </property> | ||||
|           </item> | ||||
|          </widget> | ||||
|         </item> | ||||
|        </layout> | ||||
|      <layout class="QGridLayout" name="input_inner_layout" columnstretch="1,1"> | ||||
|       <item row="0" column="0"> | ||||
|        <widget class="QLabel" name="input_type_label"> | ||||
|         <property name="text"> | ||||
|          <string>Input Type</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <layout class="QHBoxLayout" name="horizontalLayout_3"> | ||||
|         <item> | ||||
|          <widget class="QLabel" name="input_device_label"> | ||||
|           <property name="text"> | ||||
|            <string>Input Device</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item> | ||||
|          <widget class="QComboBox" name="input_device_combo_box"/> | ||||
|         </item> | ||||
|        </layout> | ||||
|       <item row="0" column="1"> | ||||
|        <widget class="QComboBox" name="input_type_combo_box"/> | ||||
|       </item> | ||||
|       <item row="1" column="0"> | ||||
|        <widget class="QLabel" name="input_device_label"> | ||||
|         <property name="text"> | ||||
|          <string>Input Device</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="1"> | ||||
|        <widget class="QComboBox" name="input_device_combo_box"/> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|  | @ -259,10 +231,10 @@ | |||
|  </widget> | ||||
|  <tabstops> | ||||
|   <tabstop>emulation_combo_box</tabstop> | ||||
|   <tabstop>output_sink_combo_box</tabstop> | ||||
|   <tabstop>toggle_audio_stretching</tabstop> | ||||
|   <tabstop>audio_device_combo_box</tabstop> | ||||
|   <tabstop>volume_slider</tabstop> | ||||
|   <tabstop>output_type_combo_box</tabstop> | ||||
|   <tabstop>output_device_combo_box</tabstop> | ||||
|   <tabstop>input_type_combo_box</tabstop> | ||||
|   <tabstop>input_device_combo_box</tabstop> | ||||
|  </tabstops> | ||||
|  |  | |||
|  | @ -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<QtImageInterface>()); | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
|     // Register microphone permission check.
 | ||||
|     system.RegisterMicPermissionCheck(&AppleAuthorization::CheckAuthorizationForMicrophone); | ||||
| #endif | ||||
| 
 | ||||
|     main_window.show(); | ||||
| 
 | ||||
|     QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window, | ||||
|  |  | |||
|  | @ -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]); | ||||
|  |  | |||
|  | @ -10,6 +10,8 @@ | |||
| #include <string> | ||||
| #include <unordered_map> | ||||
| #include <vector> | ||||
| #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<AudioEmulation> audio_emulation{AudioEmulation::HLE, "audio_emulation"}; | ||||
|     Setting<std::string> sink_id{"auto", "output_engine"}; | ||||
|     SwitchableSetting<bool> enable_audio_stretching{true, "enable_audio_stretching"}; | ||||
|     Setting<std::string> audio_device_id{"auto", "output_device"}; | ||||
|     SwitchableSetting<float, true> volume{1.f, 0.f, 1.f, "volume"}; | ||||
|     Setting<MicInputType> mic_input_type{MicInputType::None, "mic_input_type"}; | ||||
|     Setting<std::string> mic_input_device{"Default", "mic_input_device"}; | ||||
|     Setting<AudioCore::SinkType> output_type{AudioCore::SinkType::Auto, "output_type"}; | ||||
|     Setting<std::string> output_device{"auto", "output_device"}; | ||||
|     Setting<AudioCore::InputType> input_type{AudioCore::InputType::Auto, "input_type"}; | ||||
|     Setting<std::string> input_device{"auto", "input_device"}; | ||||
| 
 | ||||
|     // Camera
 | ||||
|     std::array<std::string, Service::CAM::NumCameras> camera_name; | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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<Core::TelemetrySession>(); | ||||
|  |  | |||
|  | @ -304,6 +304,17 @@ public: | |||
|         return registered_image_interface; | ||||
|     } | ||||
| 
 | ||||
|     /// Function for checking OS microphone permissions.
 | ||||
| 
 | ||||
|     void RegisterMicPermissionCheck(const std::function<bool()>& 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<bool()> mic_permission_func; | ||||
|     bool mic_permission_granted = false; | ||||
| 
 | ||||
|     friend class boost::serialization::access; | ||||
|     template <typename Archive> | ||||
|     void serialize(Archive& ar, const unsigned int file_version); | ||||
|  |  | |||
|  | @ -1,86 +0,0 @@ | |||
| // Copyright 2019 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <array> | ||||
| #include "core/frontend/mic.h" | ||||
| 
 | ||||
| #ifdef HAVE_CUBEB | ||||
| #include "audio_core/cubeb_input.h" | ||||
| #endif | ||||
| 
 | ||||
| namespace Frontend::Mic { | ||||
| 
 | ||||
| constexpr std::array<u8, 16> NOISE_SAMPLE_8_BIT = {0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|                                                    0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF}; | ||||
| 
 | ||||
| constexpr std::array<u8, 32> 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<Interface> NullFactory::Create([[maybe_unused]] std::string mic_device_name) { | ||||
|     return std::make_unique<NullMic>(); | ||||
| } | ||||
| 
 | ||||
| #ifdef HAVE_CUBEB | ||||
| static std::unique_ptr<RealMicFactory> g_factory = std::make_unique<AudioCore::CubebFactory>(); | ||||
| #else | ||||
| static std::unique_ptr<RealMicFactory> g_factory = std::make_unique<NullFactory>(); | ||||
| #endif | ||||
| 
 | ||||
| void RegisterRealMicFactory(std::unique_ptr<RealMicFactory> factory) { | ||||
|     g_factory = std::move(factory); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<Interface> CreateRealMic(std::string mic_device_name) { | ||||
|     return g_factory->Create(std::move(mic_device_name)); | ||||
| } | ||||
| 
 | ||||
| } // namespace Frontend::Mic
 | ||||
|  | @ -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 <memory> | ||||
| #include <vector> | ||||
| #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<u8>; | ||||
| 
 | ||||
| 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<u8> CACHE_8_BIT; | ||||
|     std::vector<u8> CACHE_16_BIT; | ||||
| }; | ||||
| 
 | ||||
| /// Factory for creating a real Mic input device;
 | ||||
| class RealMicFactory { | ||||
| public: | ||||
|     virtual ~RealMicFactory(); | ||||
| 
 | ||||
|     virtual std::unique_ptr<Interface> Create(std::string mic_device_name) = 0; | ||||
| }; | ||||
| 
 | ||||
| class NullFactory final : public RealMicFactory { | ||||
| public: | ||||
|     ~NullFactory() override; | ||||
| 
 | ||||
|     std::unique_ptr<Interface> Create(std::string mic_device_name) override; | ||||
| }; | ||||
| 
 | ||||
| void RegisterRealMicFactory(std::unique_ptr<RealMicFactory> factory); | ||||
| 
 | ||||
| std::unique_ptr<Interface> CreateRealMic(std::string mic_device_name); | ||||
| 
 | ||||
| } // namespace Frontend::Mic
 | ||||
|  | @ -3,11 +3,12 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <boost/serialization/weak_ptr.hpp> | ||||
| #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<u32>(state.size)}); | ||||
|  | @ -349,21 +350,9 @@ struct MIC_U::Impl { | |||
|     } | ||||
| 
 | ||||
|     void CreateMic() { | ||||
|         std::unique_ptr<Frontend::Mic::Interface> new_mic; | ||||
|         switch (Settings::values.mic_input_type.GetValue()) { | ||||
|         case Settings::MicInputType::None: | ||||
|             new_mic = std::make_unique<Frontend::Mic::NullMic>(); | ||||
|             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<Frontend::Mic::StaticMic>(); | ||||
|             break; | ||||
|         default: | ||||
|             LOG_CRITICAL(Audio, "Mic type not found. Defaulting to null mic"); | ||||
|             new_mic = std::make_unique<Frontend::Mic::NullMic>(); | ||||
|         } | ||||
|         std::unique_ptr<AudioCore::Input> 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<Frontend::Mic::Interface> mic; | ||||
|     std::unique_ptr<AudioCore::Input> mic; | ||||
|     Core::Timing& timing; | ||||
|     State state{}; | ||||
|     Encoding encoding{}; | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
|  | @ -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<int>(Settings::values.output_type.GetValue())); | ||||
|     AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching", | ||||
|              Settings::values.enable_audio_stretching.GetValue()); | ||||
|     AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue