mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40: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"] | [submodule "dds-ktx"] | ||||||
|     path = externals/dds-ktx |     path = externals/dds-ktx | ||||||
|     url = https://github.com/septag/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() | endif() | ||||||
| 
 | 
 | ||||||
| option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) | 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) | 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) |     add_subdirectory(libyuv) | ||||||
|     target_include_directories(yuv INTERFACE ./libyuv/include) |     target_include_directories(yuv INTERFACE ./libyuv/include) | ||||||
| endif() | 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); |         SettingSection audioSection = mSettings.getSection(Settings.SECTION_AUDIO); | ||||||
|         Setting audioStretch = audioSection.getSetting(SettingsFile.KEY_ENABLE_AUDIO_STRETCHING); |         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 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) { |     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_PRELOAD_TEXTURES = "preload_textures"; | ||||||
|     public static final String KEY_ASYNC_CUSTOM_LOADING = "async_custom_loading"; |     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_ENABLE_AUDIO_STRETCHING = "enable_audio_stretching"; | ||||||
|     public static final String KEY_VOLUME = "volume"; |     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"; |     public static final String KEY_USE_VIRTUAL_SD = "use_virtual_sd"; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -25,8 +25,6 @@ add_library(citra-android SHARED | ||||||
|     game_settings.h |     game_settings.h | ||||||
|     id_cache.cpp |     id_cache.cpp | ||||||
|     id_cache.h |     id_cache.h | ||||||
|     mic.cpp |  | ||||||
|     mic.h |  | ||||||
|     native.cpp |     native.cpp | ||||||
|     native.h |     native.h | ||||||
|     ndk_motion.cpp |     ndk_motion.cpp | ||||||
|  |  | ||||||
|  | @ -199,12 +199,12 @@ void Config::ReadValues() { | ||||||
| 
 | 
 | ||||||
|     // Audio
 |     // Audio
 | ||||||
|     ReadSetting("Audio", Settings::values.audio_emulation); |     ReadSetting("Audio", Settings::values.audio_emulation); | ||||||
|     ReadSetting("Audio", Settings::values.sink_id); |  | ||||||
|     ReadSetting("Audio", Settings::values.enable_audio_stretching); |     ReadSetting("Audio", Settings::values.enable_audio_stretching); | ||||||
|     ReadSetting("Audio", Settings::values.audio_device_id); |  | ||||||
|     ReadSetting("Audio", Settings::values.volume); |     ReadSetting("Audio", Settings::values.volume); | ||||||
|     ReadSetting("Audio", Settings::values.mic_input_device); |     ReadSetting("Audio", Settings::values.output_type); | ||||||
|     ReadSetting("Audio", Settings::values.mic_input_type); |     ReadSetting("Audio", Settings::values.output_device); | ||||||
|  |     ReadSetting("Audio", Settings::values.input_type); | ||||||
|  |     ReadSetting("Audio", Settings::values.input_device); | ||||||
| 
 | 
 | ||||||
|     // Data Storage
 |     // Data Storage
 | ||||||
|     ReadSetting("Data Storage", Settings::values.use_virtual_sd); |     ReadSetting("Data Storage", Settings::values.use_virtual_sd); | ||||||
|  |  | ||||||
|  | @ -226,28 +226,32 @@ enable_dsp_lle = | ||||||
| # 0 (default): No, 1: Yes | # 0 (default): No, 1: Yes | ||||||
| enable_dsp_lle_thread = | 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. | # 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, | # This effect adjusts audio speed to match emulation speed and helps prevent audio stutter, | ||||||
| # at the cost of increasing audio latency. | # at the cost of increasing audio latency. | ||||||
| # 0: No, 1 (default): Yes | # 0: No, 1 (default): Yes | ||||||
| enable_audio_stretching = | 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. | # Output volume. | ||||||
| # 1.0 (default): 100%, 0.0; mute | # 1.0 (default): 100%, 0.0; mute | ||||||
| volume = | 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] | [Data Storage] | ||||||
| # Whether to create a virtual SD card. | # Whether to create a virtual SD card. | ||||||
| # 1 (default): Yes, 0: No | # 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/core.h" | ||||||
| #include "core/frontend/applets/default_applets.h" | #include "core/frontend/applets/default_applets.h" | ||||||
| #include "core/frontend/camera/factory.h" | #include "core/frontend/camera/factory.h" | ||||||
| #include "core/frontend/mic.h" |  | ||||||
| #include "core/hle/service/am/am.h" | #include "core/hle/service/am/am.h" | ||||||
| #include "core/hle/service/nfc/nfc.h" | #include "core/hle/service/nfc/nfc.h" | ||||||
| #include "core/savestate.h" | #include "core/savestate.h" | ||||||
|  | @ -39,7 +38,6 @@ | ||||||
| #include "jni/game_settings.h" | #include "jni/game_settings.h" | ||||||
| #include "jni/id_cache.h" | #include "jni/id_cache.h" | ||||||
| #include "jni/input_manager.h" | #include "jni/input_manager.h" | ||||||
| #include "jni/mic.h" |  | ||||||
| #include "jni/native.h" | #include "jni/native.h" | ||||||
| #include "jni/ndk_motion.h" | #include "jni/ndk_motion.h" | ||||||
| #include "video_core/renderer_base.h" | #include "video_core/renderer_base.h" | ||||||
|  | @ -134,6 +132,11 @@ static void TryShutdown() { | ||||||
|     MicroProfileShutdown(); |     MicroProfileShutdown(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static bool CheckMicPermission() { | ||||||
|  |     return IDCache::GetEnvForThread()->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(), | ||||||
|  |                                                                IDCache::GetRequestMicPermission()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static Core::System::ResultStatus RunCitra(const std::string& filepath) { | static Core::System::ResultStatus RunCitra(const std::string& filepath) { | ||||||
|     // Citra core only supports a single running instance
 |     // Citra core only supports a single running instance
 | ||||||
|     std::lock_guard<std::mutex> lock(running_mutex); |     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.RegisterMiiSelector(std::make_shared<MiiSelector::AndroidMiiSelector>()); | ||||||
|     system.RegisterSoftwareKeyboard(std::make_shared<SoftwareKeyboard::AndroidKeyboard>()); |     system.RegisterSoftwareKeyboard(std::make_shared<SoftwareKeyboard::AndroidKeyboard>()); | ||||||
| 
 | 
 | ||||||
|     // Register real Mic factory
 |     // Register microphone permission check
 | ||||||
|     Frontend::Mic::RegisterRealMicFactory(std::make_unique<Mic::AndroidFactory>()); |     Core::System::GetInstance().RegisterMicPermissionCheck(&CheckMicPermission); | ||||||
| 
 | 
 | ||||||
|     InputManager::Init(); |     InputManager::Init(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -143,15 +143,19 @@ | ||||||
|     </integer-array> |     </integer-array> | ||||||
| 
 | 
 | ||||||
|     <string-array name="audioInputTypeNames"> |     <string-array name="audioInputTypeNames"> | ||||||
|  |         <item>Auto</item> | ||||||
|         <item>None</item> |         <item>None</item> | ||||||
|         <item>Real Device</item> |  | ||||||
|         <item>Static Noise</item> |         <item>Static Noise</item> | ||||||
|  |         <item>Real Device (Cubeb)</item> | ||||||
|  |         <item>Real Device (OpenAL)</item> | ||||||
|     </string-array> |     </string-array> | ||||||
| 
 | 
 | ||||||
|     <integer-array name="audioInputTypeValues"> |     <integer-array name="audioInputTypeValues"> | ||||||
|         <item>0</item> |         <item>0</item> | ||||||
|         <item>1</item> |         <item>1</item> | ||||||
|         <item>2</item> |         <item>2</item> | ||||||
|  |         <item>3</item> | ||||||
|  |         <item>4</item> | ||||||
|     </integer-array> |     </integer-array> | ||||||
| 
 | 
 | ||||||
|     <string-array name="render3dModes"> |     <string-array name="render3dModes"> | ||||||
|  |  | ||||||
|  | @ -20,18 +20,25 @@ add_library(audio_core STATIC | ||||||
|     hle/source.h |     hle/source.h | ||||||
|     lle/lle.cpp |     lle/lle.cpp | ||||||
|     lle/lle.h |     lle/lle.h | ||||||
|  |     input.h | ||||||
|  |     input_details.cpp | ||||||
|  |     input_details.h | ||||||
|     interpolate.cpp |     interpolate.cpp | ||||||
|     interpolate.h |     interpolate.h | ||||||
|  |     null_input.h | ||||||
|     null_sink.h |     null_sink.h | ||||||
|     precompiled_headers.h |     precompiled_headers.h | ||||||
|     sink.h |     sink.h | ||||||
|     sink_details.cpp |     sink_details.cpp | ||||||
|     sink_details.h |     sink_details.h | ||||||
|  |     static_input.cpp | ||||||
|  |     static_input.h | ||||||
|     time_stretch.cpp |     time_stretch.cpp | ||||||
|     time_stretch.h |     time_stretch.h | ||||||
| 
 | 
 | ||||||
|     $<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.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_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) | create_target_directory_groups(audio_core) | ||||||
|  | @ -92,6 +99,12 @@ if(ENABLE_CUBEB) | ||||||
|     target_compile_definitions(audio_core PUBLIC HAVE_CUBEB) |     target_compile_definitions(audio_core PUBLIC HAVE_CUBEB) | ||||||
| endif() | 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) | if (CITRA_USE_PRECOMPILED_HEADERS) | ||||||
|     target_precompile_headers(audio_core PRIVATE precompiled_headers.h) |     target_precompile_headers(audio_core PRIVATE precompiled_headers.h) | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
|  | @ -6,11 +6,14 @@ | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <cubeb/cubeb.h> | #include <cubeb/cubeb.h> | ||||||
| #include "audio_core/cubeb_input.h" | #include "audio_core/cubeb_input.h" | ||||||
|  | #include "audio_core/input.h" | ||||||
|  | #include "audio_core/sink.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
|  | #include "common/threadsafe_queue.h" | ||||||
| 
 | 
 | ||||||
| namespace AudioCore { | namespace AudioCore { | ||||||
| 
 | 
 | ||||||
| using SampleQueue = Common::SPSCQueue<Frontend::Mic::Samples>; | using SampleQueue = Common::SPSCQueue<Samples>; | ||||||
| 
 | 
 | ||||||
| struct CubebInput::Impl { | struct CubebInput::Impl { | ||||||
|     cubeb* ctx = nullptr; |     cubeb* ctx = nullptr; | ||||||
|  | @ -48,10 +51,10 @@ CubebInput::~CubebInput() { | ||||||
|     cubeb_destroy(impl->ctx); |     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)
 |     // Cubeb apparently only supports signed 16 bit PCM (and float32 which the 3ds doesn't support)
 | ||||||
|     // TODO resample the input stream
 |     // TODO resample the input stream
 | ||||||
|     if (params.sign == Frontend::Mic::Signedness::Unsigned) { |     if (params.sign == Signedness::Unsigned) { | ||||||
|         LOG_ERROR(Audio, |         LOG_ERROR(Audio, | ||||||
|                   "Application requested unsupported unsigned pcm format. Falling back to signed"); |                   "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; |     is_sampling = true; | ||||||
| 
 | 
 | ||||||
|     cubeb_devid input_device = nullptr; |     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; |         cubeb_device_collection collection; | ||||||
|         if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) != CUBEB_OK) { |         if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) != CUBEB_OK) { | ||||||
|             LOG_WARNING(Audio, "Audio input device enumeration not supported"); |             LOG_WARNING(Audio, "Audio input device enumeration not supported"); | ||||||
|  | @ -122,9 +125,9 @@ void CubebInput::AdjustSampleRate(u32 sample_rate) { | ||||||
|     LOG_ERROR(Audio, "AdjustSampleRate unimplemented!"); |     LOG_ERROR(Audio, "AdjustSampleRate unimplemented!"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Frontend::Mic::Samples CubebInput::Read() { | Samples CubebInput::Read() { | ||||||
|     Frontend::Mic::Samples samples{}; |     Samples samples{}; | ||||||
|     Frontend::Mic::Samples queue; |     Samples queue; | ||||||
|     while (impl->sample_queue->Pop(queue)) { |     while (impl->sample_queue->Pop(queue)) { | ||||||
|         samples.insert(samples.end(), queue.begin(), queue.end()); |         samples.insert(samples.end(), queue.begin(), queue.end()); | ||||||
|     } |     } | ||||||
|  | @ -190,10 +193,4 @@ std::vector<std::string> ListCubebInputDevices() { | ||||||
|     return device_list; |     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
 | } // namespace AudioCore
 | ||||||
|  |  | ||||||
|  | @ -5,23 +5,24 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
|  | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| #include "core/frontend/mic.h" | #include "audio_core/input.h" | ||||||
| 
 | 
 | ||||||
| namespace AudioCore { | namespace AudioCore { | ||||||
| 
 | 
 | ||||||
| class CubebInput final : public Frontend::Mic::Interface { | class CubebInput final : public Input { | ||||||
| public: | public: | ||||||
|     explicit CubebInput(std::string device_id); |     explicit CubebInput(std::string device_id); | ||||||
|     ~CubebInput() override; |     ~CubebInput() override; | ||||||
| 
 | 
 | ||||||
|     void StartSampling(const Frontend::Mic::Parameters& params) override; |     void StartSampling(const InputParameters& params) override; | ||||||
| 
 | 
 | ||||||
|     void StopSampling() override; |     void StopSampling() override; | ||||||
| 
 | 
 | ||||||
|     void AdjustSampleRate(u32 sample_rate) override; |     void AdjustSampleRate(u32 sample_rate) override; | ||||||
| 
 | 
 | ||||||
|     Frontend::Mic::Samples Read() override; |     Samples Read() override; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     struct Impl; |     struct Impl; | ||||||
|  | @ -31,11 +32,4 @@ private: | ||||||
| 
 | 
 | ||||||
| std::vector<std::string> ListCubebInputDevices(); | 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
 | } // namespace AudioCore
 | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <cstddef> | #include <cstddef> | ||||||
| #include <memory> | #include <memory> | ||||||
|  | #include <string> | ||||||
| #include "audio_core/sink.h" | #include "audio_core/sink.h" | ||||||
| 
 | 
 | ||||||
| namespace AudioCore { | namespace AudioCore { | ||||||
|  |  | ||||||
|  | @ -16,9 +16,8 @@ namespace AudioCore { | ||||||
| DspInterface::DspInterface() = default; | DspInterface::DspInterface() = default; | ||||||
| DspInterface::~DspInterface() = default; | DspInterface::~DspInterface() = default; | ||||||
| 
 | 
 | ||||||
| void DspInterface::SetSink(std::string_view sink_id, std::string_view audio_device) { | void DspInterface::SetSink(AudioCore::SinkType sink_type, std::string_view audio_device) { | ||||||
|     sink = CreateSinkFromID(Settings::values.sink_id.GetValue(), |     sink = CreateSinkFromID(sink_type, audio_device); | ||||||
|                             Settings::values.audio_device_id.GetValue()); |  | ||||||
|     sink->SetCallback( |     sink->SetCallback( | ||||||
|         [this](s16* buffer, std::size_t num_frames) { OutputCallback(buffer, num_frames); }); |         [this](s16* buffer, std::size_t num_frames) { OutputCallback(buffer, num_frames); }); | ||||||
|     time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); |     time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ class DSP_DSP; | ||||||
| namespace AudioCore { | namespace AudioCore { | ||||||
| 
 | 
 | ||||||
| class Sink; | class Sink; | ||||||
|  | enum class SinkType : u32; | ||||||
| 
 | 
 | ||||||
| class DspInterface { | class DspInterface { | ||||||
| public: | public: | ||||||
|  | @ -93,8 +94,8 @@ public: | ||||||
|     /// Unloads the DSP program
 |     /// Unloads the DSP program
 | ||||||
|     virtual void UnloadComponent() = 0; |     virtual void UnloadComponent() = 0; | ||||||
| 
 | 
 | ||||||
|     /// Select the sink to use based on sink id.
 |     /// Select the sink to use based on sink type.
 | ||||||
|     void SetSink(std::string_view sink_id, std::string_view audio_device); |     void SetSink(SinkType sink_type, std::string_view audio_device); | ||||||
|     /// Get the current sink
 |     /// Get the current sink
 | ||||||
|     Sink& GetSink(); |     Sink& GetSink(); | ||||||
|     /// Enable/Disable audio stretching.
 |     /// 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 | #ifdef HAVE_CUBEB | ||||||
| #include "audio_core/cubeb_sink.h" | #include "audio_core/cubeb_sink.h" | ||||||
| #endif | #endif | ||||||
|  | #ifdef HAVE_OPENAL | ||||||
|  | #include "audio_core/openal_sink.h" | ||||||
|  | #endif | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| 
 | 
 | ||||||
| namespace AudioCore { | namespace AudioCore { | ||||||
|  | @ -22,8 +25,10 @@ struct SinkDetails { | ||||||
|     using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); |     using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); | ||||||
|     using ListDevicesFn = std::vector<std::string> (*)(); |     using ListDevicesFn = std::vector<std::string> (*)(); | ||||||
| 
 | 
 | ||||||
|  |     /// Type of this sink.
 | ||||||
|  |     SinkType type; | ||||||
|     /// Name for this sink.
 |     /// Name for this sink.
 | ||||||
|     const char* id; |     std::string_view name; | ||||||
|     /// A method to call to construct an instance of this type of sink.
 |     /// A method to call to construct an instance of this type of sink.
 | ||||||
|     FactoryFn factory; |     FactoryFn factory; | ||||||
|     /// A method to call to list available devices.
 |     /// 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.
 | // 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 | #ifdef HAVE_CUBEB | ||||||
|     SinkDetails{"cubeb", |     SinkDetails{SinkType::Cubeb, "Cubeb", | ||||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { |                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||||
|                     return std::make_unique<CubebSink>(device_id); |                     return std::make_unique<CubebSink>(device_id); | ||||||
|                 }, |                 }, | ||||||
|                 &ListCubebSinkDevices}, |                 &ListCubebSinkDevices}, | ||||||
| #endif | #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 | #ifdef HAVE_SDL2 | ||||||
|     SinkDetails{"sdl2", |     SinkDetails{SinkType::SDL2, "SDL2", | ||||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { |                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||||
|                     return std::make_unique<SDL2Sink>(std::string(device_id)); |                     return std::make_unique<SDL2Sink>(std::string(device_id)); | ||||||
|                 }, |                 }, | ||||||
|                 &ListSDL2SinkDevices}, |                 &ListSDL2SinkDevices}, | ||||||
| #endif | #endif | ||||||
|     SinkDetails{"null", |     SinkDetails{SinkType::Null, "None", | ||||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { |                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||||
|                     return std::make_unique<NullSink>(device_id); |                     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) { | const SinkDetails& GetSinkDetails(SinkType sink_type) { | ||||||
|     auto iter = |     auto iter = std::find_if( | ||||||
|         std::find_if(std::begin(sink_details), std::end(sink_details), |         sink_details.begin(), sink_details.end(), | ||||||
|                      [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); |         [sink_type](const auto& sink_detail) { return sink_detail.type == sink_type; }); | ||||||
| 
 | 
 | ||||||
|     if (sink_id == "auto" || iter == std::end(sink_details)) { |     if (sink_type == SinkType::Auto || iter == sink_details.end()) { | ||||||
|         if (sink_id != "auto") { |         if (sink_type != SinkType::Auto) { | ||||||
|             LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id); |             LOG_ERROR(Audio, "AudioCore::GetSinkDetails given invalid sink_type {}", sink_type); | ||||||
|         } |         } | ||||||
|         // Auto-select.
 |         // Auto-select.
 | ||||||
|         // sink_details is ordered in terms of desirability, with the best choice at the front.
 |         // 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; |     return *iter; | ||||||
| } | } | ||||||
| } // Anonymous namespace
 | } // Anonymous namespace
 | ||||||
| 
 | 
 | ||||||
| std::vector<const char*> GetSinkIDs() { | std::string_view GetSinkName(SinkType sink_type) { | ||||||
|     std::vector<const char*> sink_ids(std::size(sink_details)); |     if (sink_type == SinkType::Auto) { | ||||||
| 
 |         return "Auto"; | ||||||
|     std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids), |     } | ||||||
|                    [](const auto& sink) { return sink.id; }); |     return GetSinkDetails(sink_type).name; | ||||||
| 
 |  | ||||||
|     return sink_ids; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) { | std::vector<std::string> GetDeviceListForSink(SinkType sink_type) { | ||||||
|     return GetSinkDetails(sink_id).list_devices(); |     return GetSinkDetails(sink_type).list_devices(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 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) { | ||||||
|     return GetSinkDetails(sink_id).factory(device_id); |     return GetSinkDetails(sink_type).factory(device_id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace AudioCore
 | } // namespace AudioCore
 | ||||||
|  |  | ||||||
|  | @ -4,21 +4,33 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include <string_view> | #include <string_view> | ||||||
| #include <vector> | #include <vector> | ||||||
|  | #include "common/common_types.h" | ||||||
| 
 | 
 | ||||||
| namespace AudioCore { | namespace AudioCore { | ||||||
| 
 | 
 | ||||||
| class Sink; | class Sink; | ||||||
| 
 | 
 | ||||||
| /// Retrieves the IDs for all available audio sinks.
 | enum class SinkType : u32 { | ||||||
| std::vector<const char*> GetSinkIDs(); |     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.
 | /// 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.
 | /// 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
 | } // 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/logging/log.h" | ||||||
| #include "common/param_package.h" | #include "common/param_package.h" | ||||||
| #include "common/settings.h" | #include "common/settings.h" | ||||||
| #include "core/frontend/mic.h" |  | ||||||
| #include "core/hle/service/service.h" | #include "core/hle/service/service.h" | ||||||
| #include "input_common/main.h" | #include "input_common/main.h" | ||||||
| #include "input_common/udp/client.h" | #include "input_common/udp/client.h" | ||||||
|  | @ -179,12 +178,12 @@ void Config::ReadValues() { | ||||||
| 
 | 
 | ||||||
|     // Audio
 |     // Audio
 | ||||||
|     ReadSetting("Audio", Settings::values.audio_emulation); |     ReadSetting("Audio", Settings::values.audio_emulation); | ||||||
|     ReadSetting("Audio", Settings::values.sink_id); |  | ||||||
|     ReadSetting("Audio", Settings::values.enable_audio_stretching); |     ReadSetting("Audio", Settings::values.enable_audio_stretching); | ||||||
|     ReadSetting("Audio", Settings::values.audio_device_id); |  | ||||||
|     ReadSetting("Audio", Settings::values.volume); |     ReadSetting("Audio", Settings::values.volume); | ||||||
|     ReadSetting("Audio", Settings::values.mic_input_device); |     ReadSetting("Audio", Settings::values.output_type); | ||||||
|     ReadSetting("Audio", Settings::values.mic_input_type); |     ReadSetting("Audio", Settings::values.output_device); | ||||||
|  |     ReadSetting("Audio", Settings::values.input_type); | ||||||
|  |     ReadSetting("Audio", Settings::values.input_device); | ||||||
| 
 | 
 | ||||||
|     // Data Storage
 |     // Data Storage
 | ||||||
|     ReadSetting("Data Storage", Settings::values.use_virtual_sd); |     ReadSetting("Data Storage", Settings::values.use_virtual_sd); | ||||||
|  |  | ||||||
|  | @ -245,25 +245,32 @@ enable_dsp_lle = | ||||||
| # 0 (default): No, 1: Yes | # 0 (default): No, 1: Yes | ||||||
| enable_dsp_lle_thread = | 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. | # 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, | # This effect adjusts audio speed to match emulation speed and helps prevent audio stutter, | ||||||
| # at the cost of increasing audio latency. | # at the cost of increasing audio latency. | ||||||
| # 0: No, 1 (default): Yes | # 0: No, 1 (default): Yes | ||||||
| enable_audio_stretching = | enable_audio_stretching = | ||||||
| 
 | 
 | ||||||
| # Which audio device to use. |  | ||||||
| # auto (default): Auto-select |  | ||||||
| output_device = |  | ||||||
| 
 |  | ||||||
| # Output volume. | # Output volume. | ||||||
| # 1.0 (default): 100%, 0.0; mute | # 1.0 (default): 100%, 0.0; mute | ||||||
| volume = | 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] | [Data Storage] | ||||||
| # Whether to create a virtual SD card. | # Whether to create a virtual SD card. | ||||||
| # 1 (default): Yes, 0: No | # 1 (default): Yes, 0: No | ||||||
|  |  | ||||||
|  | @ -10,7 +10,6 @@ | ||||||
| #include "citra_qt/configuration/config.h" | #include "citra_qt/configuration/config.h" | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
| #include "common/settings.h" | #include "common/settings.h" | ||||||
| #include "core/frontend/mic.h" |  | ||||||
| #include "core/hle/service/service.h" | #include "core/hle/service/service.h" | ||||||
| #include "input_common/main.h" | #include "input_common/main.h" | ||||||
| #include "input_common/udp/client.h" | #include "input_common/udp/client.h" | ||||||
|  | @ -276,10 +275,10 @@ void Config::ReadAudioValues() { | ||||||
|     ReadGlobalSetting(Settings::values.volume); |     ReadGlobalSetting(Settings::values.volume); | ||||||
| 
 | 
 | ||||||
|     if (global) { |     if (global) { | ||||||
|         ReadBasicSetting(Settings::values.sink_id); |         ReadBasicSetting(Settings::values.output_type); | ||||||
|         ReadBasicSetting(Settings::values.audio_device_id); |         ReadBasicSetting(Settings::values.output_device); | ||||||
|         ReadBasicSetting(Settings::values.mic_input_device); |         ReadBasicSetting(Settings::values.input_type); | ||||||
|         ReadBasicSetting(Settings::values.mic_input_type); |         ReadBasicSetting(Settings::values.input_device); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
|  | @ -847,10 +846,10 @@ void Config::SaveAudioValues() { | ||||||
|     WriteGlobalSetting(Settings::values.volume); |     WriteGlobalSetting(Settings::values.volume); | ||||||
| 
 | 
 | ||||||
|     if (global) { |     if (global) { | ||||||
|         WriteBasicSetting(Settings::values.sink_id); |         WriteBasicSetting(Settings::values.output_type); | ||||||
|         WriteBasicSetting(Settings::values.audio_device_id); |         WriteBasicSetting(Settings::values.output_device); | ||||||
|         WriteBasicSetting(Settings::values.mic_input_device); |         WriteBasicSetting(Settings::values.input_type); | ||||||
|         WriteBasicSetting(Settings::values.mic_input_type); |         WriteBasicSetting(Settings::values.input_device); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
|  |  | ||||||
|  | @ -4,32 +4,27 @@ | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <QtGlobal> | #include <QtGlobal> | ||||||
| #ifdef HAVE_CUBEB | #include "audio_core/input_details.h" | ||||||
| #include "audio_core/cubeb_input.h" |  | ||||||
| #endif |  | ||||||
| #include "audio_core/sink.h" | #include "audio_core/sink.h" | ||||||
| #include "audio_core/sink_details.h" | #include "audio_core/sink_details.h" | ||||||
| #include "citra_qt/configuration/configuration_shared.h" | #include "citra_qt/configuration/configuration_shared.h" | ||||||
| #include "citra_qt/configuration/configure_audio.h" | #include "citra_qt/configuration/configure_audio.h" | ||||||
| #include "common/settings.h" | #include "common/settings.h" | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
| #include "core/frontend/mic.h" |  | ||||||
| #include "ui_configure_audio.h" | #include "ui_configure_audio.h" | ||||||
| 
 | 
 | ||||||
| #if defined(__APPLE__) | #if defined(__APPLE__) | ||||||
| #include "citra_qt/macos_authorization.h" | #include "citra_qt/macos_authorization.h" | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| constexpr int DEFAULT_INPUT_DEVICE_INDEX = 0; |  | ||||||
| 
 |  | ||||||
| ConfigureAudio::ConfigureAudio(QWidget* parent) | ConfigureAudio::ConfigureAudio(QWidget* parent) | ||||||
|     : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()) { |     : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()) { | ||||||
|     ui->setupUi(this); |     ui->setupUi(this); | ||||||
| 
 | 
 | ||||||
|     ui->output_sink_combo_box->clear(); |     ui->output_type_combo_box->clear(); | ||||||
|     ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); |     for (u32 type = 0; type < static_cast<u32>(AudioCore::SinkType::NumSinkTypes); type++) { | ||||||
|     for (const char* id : AudioCore::GetSinkIDs()) { |         ui->output_type_combo_box->addItem(QString::fromUtf8( | ||||||
|         ui->output_sink_combo_box->addItem(QString::fromUtf8(id)); |             AudioCore::GetSinkName(static_cast<AudioCore::SinkType>(type)).data())); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const bool is_running = Core::System::GetInstance().IsPoweredOn(); |     const bool is_running = Core::System::GetInstance().IsPoweredOn(); | ||||||
|  | @ -38,17 +33,11 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) | ||||||
|     connect(ui->volume_slider, &QSlider::valueChanged, this, |     connect(ui->volume_slider, &QSlider::valueChanged, this, | ||||||
|             &ConfigureAudio::SetVolumeIndicatorText); |             &ConfigureAudio::SetVolumeIndicatorText); | ||||||
| 
 | 
 | ||||||
|     ui->input_device_combo_box->clear(); |     ui->input_type_combo_box->clear(); | ||||||
|     ui->input_device_combo_box->addItem(tr("Default")); |     for (u32 type = 0; type < static_cast<u32>(AudioCore::InputType::NumInputTypes); type++) { | ||||||
| 
 |         ui->input_type_combo_box->addItem(QString::fromUtf8( | ||||||
| #ifdef HAVE_CUBEB |             AudioCore::GetInputName(static_cast<AudioCore::InputType>(type)).data())); | ||||||
|     for (const auto& device : AudioCore::ListCubebInputDevices()) { |  | ||||||
|         ui->input_device_combo_box->addItem(QString::fromStdString(device)); |  | ||||||
|     } |     } | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
|     connect(ui->input_type_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, |  | ||||||
|             &ConfigureAudio::UpdateAudioInputDevices); |  | ||||||
| 
 | 
 | ||||||
|     ui->volume_label->setVisible(Settings::IsConfiguringGlobal()); |     ui->volume_label->setVisible(Settings::IsConfiguringGlobal()); | ||||||
|     ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal()); |     ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal()); | ||||||
|  | @ -56,18 +45,23 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) | ||||||
|     SetupPerGameUI(); |     SetupPerGameUI(); | ||||||
|     SetConfiguration(); |     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); |             &ConfigureAudio::UpdateAudioOutputDevices); | ||||||
|  |     connect(ui->input_type_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, | ||||||
|  |             &ConfigureAudio::UpdateAudioInputDevices); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ConfigureAudio::~ConfigureAudio() {} | ConfigureAudio::~ConfigureAudio() {} | ||||||
| 
 | 
 | ||||||
| void ConfigureAudio::SetConfiguration() { | void ConfigureAudio::SetConfiguration() { | ||||||
|     SetOutputSinkFromSinkID(); |     SetOutputTypeFromSinkType(); | ||||||
|  |     SetInputTypeFromInputType(); | ||||||
| 
 | 
 | ||||||
|     // The device list cannot be pre-populated (nor listed) until the output sink is known.
 |     // The device list cannot be pre-populated (nor listed) until the output sink is known.
 | ||||||
|     UpdateAudioOutputDevices(ui->output_sink_combo_box->currentIndex()); |     UpdateAudioOutputDevices(ui->output_type_combo_box->currentIndex()); | ||||||
|     SetAudioDeviceFromDeviceID(); |     UpdateAudioInputDevices(ui->input_type_combo_box->currentIndex()); | ||||||
|  |     SetOutputDeviceFromDeviceID(); | ||||||
|  |     SetInputDeviceFromDeviceID(); | ||||||
| 
 | 
 | ||||||
|     ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue()); |     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()); |         s32 selection = static_cast<s32>(Settings::values.audio_emulation.GetValue()); | ||||||
|         ui->emulation_combo_box->setCurrentIndex(selection); |         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() { | void ConfigureAudio::SetOutputTypeFromSinkType() { | ||||||
|     int new_sink_index = 0; |     ui->output_type_combo_box->setCurrentIndex( | ||||||
| 
 |         static_cast<int>(Settings::values.output_type.GetValue())); | ||||||
|     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::SetAudioDeviceFromDeviceID() { | void ConfigureAudio::SetOutputDeviceFromDeviceID() { | ||||||
|     int new_device_index = -1; |     int new_device_index = -1; | ||||||
| 
 | 
 | ||||||
|     const QString device_id = QString::fromStdString(Settings::values.audio_device_id.GetValue()); |     const QString device_id = QString::fromStdString(Settings::values.output_device.GetValue()); | ||||||
|     for (int index = 0; index < ui->audio_device_combo_box->count(); index++) { |     for (int index = 0; index < ui->output_device_combo_box->count(); index++) { | ||||||
|         if (ui->audio_device_combo_box->itemText(index) == device_id) { |         if (ui->output_device_combo_box->itemText(index) == device_id) { | ||||||
|             new_device_index = index; |             new_device_index = index; | ||||||
|             break; |             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) { | void ConfigureAudio::SetVolumeIndicatorText(int percentage) { | ||||||
|  | @ -144,43 +143,40 @@ void ConfigureAudio::ApplyConfiguration() { | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|     if (Settings::IsConfiguringGlobal()) { |     if (Settings::IsConfiguringGlobal()) { | ||||||
|         Settings::values.sink_id = |         Settings::values.output_type = | ||||||
|             ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) |             static_cast<AudioCore::SinkType>(ui->output_type_combo_box->currentIndex()); | ||||||
|                 .toStdString(); |         Settings::values.output_device = ui->output_device_combo_box->currentText().toStdString(); | ||||||
|         Settings::values.audio_device_id = |         Settings::values.input_type = | ||||||
|             ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) |             static_cast<AudioCore::InputType>(ui->input_type_combo_box->currentIndex()); | ||||||
|                 .toStdString(); |         Settings::values.input_device = ui->input_device_combo_box->currentText().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(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ConfigureAudio::UpdateAudioOutputDevices(int sink_index) { | void ConfigureAudio::UpdateAudioOutputDevices(int sink_index) { | ||||||
|     ui->audio_device_combo_box->clear(); |     auto sink_type = static_cast<AudioCore::SinkType>(sink_index); | ||||||
|     ui->audio_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); |  | ||||||
| 
 | 
 | ||||||
|     const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); |     ui->output_device_combo_box->clear(); | ||||||
|     for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) { |     ui->output_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); | ||||||
|         ui->audio_device_combo_box->addItem(QString::fromStdString(device)); | 
 | ||||||
|  |     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 defined(__APPLE__) | ||||||
|     if (index == 1) { |     if (input_type != AudioCore::InputType::Null && input_type != AudioCore::InputType::Static) { | ||||||
|         AppleAuthorization::CheckAuthorizationForMicrophone(); |         AppleAuthorization::CheckAuthorizationForMicrophone(); | ||||||
|     } |     } | ||||||
| #endif | #endif | ||||||
|     if (Settings::values.mic_input_device.GetValue() != Frontend::Mic::default_device_name) { | 
 | ||||||
|         ui->input_device_combo_box->setCurrentText( |     ui->input_device_combo_box->clear(); | ||||||
|             QString::fromStdString(Settings::values.mic_input_device.GetValue())); |     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; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ui->output_sink_combo_box->setVisible(false); |     ui->output_type_combo_box->setVisible(false); | ||||||
|     ui->output_sink_label->setVisible(false); |     ui->output_type_label->setVisible(false); | ||||||
|     ui->audio_device_combo_box->setVisible(false); |     ui->output_device_combo_box->setVisible(false); | ||||||
|     ui->audio_device_label->setVisible(false); |     ui->output_device_label->setVisible(false); | ||||||
|     ui->input_type_label->setVisible(false); |     ui->input_type_label->setVisible(false); | ||||||
|     ui->input_type_combo_box->setVisible(false); |     ui->input_type_combo_box->setVisible(false); | ||||||
|     ui->input_device_label->setVisible(false); |     ui->input_device_label->setVisible(false); | ||||||
|     ui->input_device_combo_box->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) { |     connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) { | ||||||
|         ui->volume_slider->setEnabled(index == 1); |         ui->volume_slider->setEnabled(index == 1); | ||||||
|  |  | ||||||
|  | @ -30,8 +30,10 @@ private: | ||||||
|     void UpdateAudioOutputDevices(int sink_index); |     void UpdateAudioOutputDevices(int sink_index); | ||||||
|     void UpdateAudioInputDevices(int index); |     void UpdateAudioInputDevices(int index); | ||||||
| 
 | 
 | ||||||
|     void SetOutputSinkFromSinkID(); |     void SetOutputTypeFromSinkType(); | ||||||
|     void SetAudioDeviceFromDeviceID(); |     void SetOutputDeviceFromDeviceID(); | ||||||
|  |     void SetInputTypeFromInputType(); | ||||||
|  |     void SetInputDeviceFromDeviceID(); | ||||||
|     void SetVolumeIndicatorText(int percentage); |     void SetVolumeIndicatorText(int percentage); | ||||||
| 
 | 
 | ||||||
|     void SetupPerGameUI(); |     void SetupPerGameUI(); | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ | ||||||
|    <item> |    <item> | ||||||
|     <widget class="QGroupBox" name="groupBox"> |     <widget class="QGroupBox" name="groupBox"> | ||||||
|      <property name="title"> |      <property name="title"> | ||||||
|       <string>Audio</string> |       <string>Output</string> | ||||||
|      </property> |      </property> | ||||||
|      <layout class="QVBoxLayout"> |      <layout class="QVBoxLayout"> | ||||||
|       <item> |       <item> | ||||||
|  | @ -62,16 +62,26 @@ | ||||||
|        </widget> |        </widget> | ||||||
|       </item> |       </item> | ||||||
|       <item> |       <item> | ||||||
|        <layout class="QHBoxLayout" name="output_engine_layout"> |        <layout class="QGridLayout" name="output_layout" columnstretch="1,1"> | ||||||
|         <item> |         <item row="0" column="0"> | ||||||
|          <widget class="QLabel" name="output_sink_label"> |          <widget class="QLabel" name="output_type_label"> | ||||||
|           <property name="text"> |           <property name="text"> | ||||||
|            <string>Output Engine</string> |            <string>Output Type</string> | ||||||
|           </property> |           </property> | ||||||
|          </widget> |          </widget> | ||||||
|         </item> |         </item> | ||||||
|         <item> |         <item row="0" column="1"> | ||||||
|          <widget class="QComboBox" name="output_sink_combo_box"/> |          <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> |         </item> | ||||||
|        </layout> |        </layout> | ||||||
|       </item> |       </item> | ||||||
|  | @ -85,20 +95,6 @@ | ||||||
|         </property> |         </property> | ||||||
|        </widget> |        </widget> | ||||||
|       </item> |       </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> |       <item> | ||||||
|        <widget class="QWidget" name="volume_layout" native="true"> |        <widget class="QWidget" name="volume_layout" native="true"> | ||||||
|         <layout class="QHBoxLayout" name="horizontalLayout_2"> |         <layout class="QHBoxLayout" name="horizontalLayout_2"> | ||||||
|  | @ -190,56 +186,32 @@ | ||||||
|     </widget> |     </widget> | ||||||
|    </item> |    </item> | ||||||
|    <item> |    <item> | ||||||
|     <widget class="QGroupBox" name="microphone_layout"> |     <widget class="QGroupBox" name="input_layout"> | ||||||
|      <property name="title"> |      <property name="title"> | ||||||
|       <string>Microphone</string> |       <string>Microphone</string> | ||||||
|      </property> |      </property> | ||||||
|      <layout class="QVBoxLayout" name="verticalLayout"> |      <layout class="QGridLayout" name="input_inner_layout" columnstretch="1,1"> | ||||||
|       <item> |       <item row="0" column="0"> | ||||||
|        <layout class="QHBoxLayout" name="horizontalLayout"> |  | ||||||
|         <item> |  | ||||||
|        <widget class="QLabel" name="input_type_label"> |        <widget class="QLabel" name="input_type_label"> | ||||||
|         <property name="text"> |         <property name="text"> | ||||||
|          <string>Input Type</string> |          <string>Input Type</string> | ||||||
|         </property> |         </property> | ||||||
|        </widget> |        </widget> | ||||||
|       </item> |       </item> | ||||||
|         <item> |       <item row="0" column="1"> | ||||||
|          <widget class="QComboBox" name="input_type_combo_box"> |        <widget class="QComboBox" name="input_type_combo_box"/> | ||||||
|           <item> |  | ||||||
|            <property name="text"> |  | ||||||
|             <string>None</string> |  | ||||||
|            </property> |  | ||||||
|       </item> |       </item> | ||||||
|           <item> |       <item row="1" column="0"> | ||||||
|            <property name="text"> |  | ||||||
|             <string>Real Device</string> |  | ||||||
|            </property> |  | ||||||
|           </item> |  | ||||||
|           <item> |  | ||||||
|            <property name="text"> |  | ||||||
|             <string>Static Noise</string> |  | ||||||
|            </property> |  | ||||||
|           </item> |  | ||||||
|          </widget> |  | ||||||
|         </item> |  | ||||||
|        </layout> |  | ||||||
|       </item> |  | ||||||
|       <item> |  | ||||||
|        <layout class="QHBoxLayout" name="horizontalLayout_3"> |  | ||||||
|         <item> |  | ||||||
|        <widget class="QLabel" name="input_device_label"> |        <widget class="QLabel" name="input_device_label"> | ||||||
|         <property name="text"> |         <property name="text"> | ||||||
|          <string>Input Device</string> |          <string>Input Device</string> | ||||||
|         </property> |         </property> | ||||||
|        </widget> |        </widget> | ||||||
|       </item> |       </item> | ||||||
|         <item> |       <item row="1" column="1"> | ||||||
|        <widget class="QComboBox" name="input_device_combo_box"/> |        <widget class="QComboBox" name="input_device_combo_box"/> | ||||||
|       </item> |       </item> | ||||||
|      </layout> |      </layout> | ||||||
|       </item> |  | ||||||
|      </layout> |  | ||||||
|     </widget> |     </widget> | ||||||
|    </item> |    </item> | ||||||
|    <item> |    <item> | ||||||
|  | @ -259,10 +231,10 @@ | ||||||
|  </widget> |  </widget> | ||||||
|  <tabstops> |  <tabstops> | ||||||
|   <tabstop>emulation_combo_box</tabstop> |   <tabstop>emulation_combo_box</tabstop> | ||||||
|   <tabstop>output_sink_combo_box</tabstop> |  | ||||||
|   <tabstop>toggle_audio_stretching</tabstop> |   <tabstop>toggle_audio_stretching</tabstop> | ||||||
|   <tabstop>audio_device_combo_box</tabstop> |  | ||||||
|   <tabstop>volume_slider</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_type_combo_box</tabstop> | ||||||
|   <tabstop>input_device_combo_box</tabstop> |   <tabstop>input_device_combo_box</tabstop> | ||||||
|  </tabstops> |  </tabstops> | ||||||
|  |  | ||||||
|  | @ -101,6 +101,10 @@ | ||||||
| #include "video_core/renderer_base.h" | #include "video_core/renderer_base.h" | ||||||
| #include "video_core/video_core.h" | #include "video_core/video_core.h" | ||||||
| 
 | 
 | ||||||
|  | #ifdef __APPLE__ | ||||||
|  | #include "macos_authorization.h" | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #ifdef USE_DISCORD_PRESENCE | #ifdef USE_DISCORD_PRESENCE | ||||||
| #include "citra_qt/discord_impl.h" | #include "citra_qt/discord_impl.h" | ||||||
| #endif | #endif | ||||||
|  | @ -2779,6 +2783,11 @@ int main(int argc, char* argv[]) { | ||||||
|     // Register Qt image interface
 |     // Register Qt image interface
 | ||||||
|     system.RegisterImageInterface(std::make_shared<QtImageInterface>()); |     system.RegisterImageInterface(std::make_shared<QtImageInterface>()); | ||||||
| 
 | 
 | ||||||
|  | #ifdef __APPLE__ | ||||||
|  |     // Register microphone permission check.
 | ||||||
|  |     system.RegisterMicPermissionCheck(&AppleAuthorization::CheckAuthorizationForMicrophone); | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|     main_window.show(); |     main_window.show(); | ||||||
| 
 | 
 | ||||||
|     QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window, |     QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window, | ||||||
|  |  | ||||||
|  | @ -89,7 +89,7 @@ void Apply() { | ||||||
|     auto& system = Core::System::GetInstance(); |     auto& system = Core::System::GetInstance(); | ||||||
|     if (system.IsPoweredOn()) { |     if (system.IsPoweredOn()) { | ||||||
|         system.CoreTiming().UpdateClockSpeed(values.cpu_clock_percentage.GetValue()); |         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()); |         Core::DSP().EnableStretching(values.enable_audio_stretching.GetValue()); | ||||||
| 
 | 
 | ||||||
|         auto hid = Service::HID::GetModule(system); |         auto hid = Service::HID::GetModule(system); | ||||||
|  | @ -156,11 +156,11 @@ void LogSettings() { | ||||||
|     log_setting("Utility_CustomTextures", values.custom_textures.GetValue()); |     log_setting("Utility_CustomTextures", values.custom_textures.GetValue()); | ||||||
|     log_setting("Utility_UseDiskShaderCache", values.use_disk_shader_cache.GetValue()); |     log_setting("Utility_UseDiskShaderCache", values.use_disk_shader_cache.GetValue()); | ||||||
|     log_setting("Audio_Emulation", GetAudioEmulationName(values.audio_emulation.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_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; |     using namespace Service::CAM; | ||||||
|     log_setting("Camera_OuterRightName", values.camera_name[OuterRightCamera]); |     log_setting("Camera_OuterRightName", values.camera_name[OuterRightCamera]); | ||||||
|     log_setting("Camera_OuterRightConfig", values.camera_config[OuterRightCamera]); |     log_setting("Camera_OuterRightConfig", values.camera_config[OuterRightCamera]); | ||||||
|  |  | ||||||
|  | @ -10,6 +10,8 @@ | ||||||
| #include <string> | #include <string> | ||||||
| #include <unordered_map> | #include <unordered_map> | ||||||
| #include <vector> | #include <vector> | ||||||
|  | #include "audio_core/input_details.h" | ||||||
|  | #include "audio_core/sink_details.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/hle/service/cam/cam_params.h" | #include "core/hle/service/cam/cam_params.h" | ||||||
| 
 | 
 | ||||||
|  | @ -42,12 +44,6 @@ enum class LayoutOption : u32 { | ||||||
|     MobileLandscape, |     MobileLandscape, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| enum class MicInputType : u32 { |  | ||||||
|     None = 0, |  | ||||||
|     Real = 1, |  | ||||||
|     Static = 2, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| enum class StereoRenderOption : u32 { | enum class StereoRenderOption : u32 { | ||||||
|     Off = 0, |     Off = 0, | ||||||
|     SideBySide = 1, |     SideBySide = 1, | ||||||
|  | @ -482,12 +478,12 @@ struct Values { | ||||||
|     // Audio
 |     // Audio
 | ||||||
|     bool audio_muted; |     bool audio_muted; | ||||||
|     SwitchableSetting<AudioEmulation> audio_emulation{AudioEmulation::HLE, "audio_emulation"}; |     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"}; |     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"}; |     SwitchableSetting<float, true> volume{1.f, 0.f, 1.f, "volume"}; | ||||||
|     Setting<MicInputType> mic_input_type{MicInputType::None, "mic_input_type"}; |     Setting<AudioCore::SinkType> output_type{AudioCore::SinkType::Auto, "output_type"}; | ||||||
|     Setting<std::string> mic_input_device{"Default", "mic_input_device"}; |     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
 |     // Camera
 | ||||||
|     std::array<std::string, Service::CAM::NumCameras> camera_name; |     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.cpp | ||||||
|     frontend/image_interface.h |     frontend/image_interface.h | ||||||
|     frontend/input.h |     frontend/input.h | ||||||
|     frontend/mic.cpp |  | ||||||
|     frontend/mic.h |  | ||||||
|     gdbstub/gdbstub.cpp |     gdbstub/gdbstub.cpp | ||||||
|     gdbstub/gdbstub.h |     gdbstub/gdbstub.h | ||||||
|     gdbstub/hio.cpp |     gdbstub/hio.cpp | ||||||
|  |  | ||||||
|  | @ -411,8 +411,8 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, | ||||||
| 
 | 
 | ||||||
|     memory->SetDSP(*dsp_core); |     memory->SetDSP(*dsp_core); | ||||||
| 
 | 
 | ||||||
|     dsp_core->SetSink(Settings::values.sink_id.GetValue(), |     dsp_core->SetSink(Settings::values.output_type.GetValue(), | ||||||
|                       Settings::values.audio_device_id.GetValue()); |                       Settings::values.output_device.GetValue()); | ||||||
|     dsp_core->EnableStretching(Settings::values.enable_audio_stretching.GetValue()); |     dsp_core->EnableStretching(Settings::values.enable_audio_stretching.GetValue()); | ||||||
| 
 | 
 | ||||||
|     telemetry_session = std::make_unique<Core::TelemetrySession>(); |     telemetry_session = std::make_unique<Core::TelemetrySession>(); | ||||||
|  |  | ||||||
|  | @ -304,6 +304,17 @@ public: | ||||||
|         return registered_image_interface; |         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 SaveState(u32 slot) const; | ||||||
| 
 | 
 | ||||||
|     void LoadState(u32 slot); |     void LoadState(u32 slot); | ||||||
|  | @ -397,6 +408,9 @@ private: | ||||||
|     Signal current_signal; |     Signal current_signal; | ||||||
|     u32 signal_param; |     u32 signal_param; | ||||||
| 
 | 
 | ||||||
|  |     std::function<bool()> mic_permission_func; | ||||||
|  |     bool mic_permission_granted = false; | ||||||
|  | 
 | ||||||
|     friend class boost::serialization::access; |     friend class boost::serialization::access; | ||||||
|     template <typename Archive> |     template <typename Archive> | ||||||
|     void serialize(Archive& ar, const unsigned int file_version); |     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.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include <boost/serialization/weak_ptr.hpp> | #include <boost/serialization/weak_ptr.hpp> | ||||||
|  | #include "audio_core/input.h" | ||||||
|  | #include "audio_core/input_details.h" | ||||||
| #include "common/archives.h" | #include "common/archives.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/settings.h" | #include "common/settings.h" | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
| #include "core/frontend/mic.h" |  | ||||||
| #include "core/hle/ipc.h" | #include "core/hle/ipc.h" | ||||||
| #include "core/hle/ipc_helpers.h" | #include "core/hle/ipc_helpers.h" | ||||||
| #include "core/hle/kernel/event.h" | #include "core/hle/kernel/event.h" | ||||||
|  | @ -167,7 +168,7 @@ struct MIC_U::Impl { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Frontend::Mic::Samples samples = mic->Read(); |         AudioCore::Samples samples = mic->Read(); | ||||||
|         if (!samples.empty()) { |         if (!samples.empty()) { | ||||||
|             // write the samples to sharedmem page
 |             // write the samples to sharedmem page
 | ||||||
|             state.WriteSamples(samples); |             state.WriteSamples(samples); | ||||||
|  | @ -180,8 +181,8 @@ struct MIC_U::Impl { | ||||||
| 
 | 
 | ||||||
|     void StartSampling() { |     void StartSampling() { | ||||||
|         auto sign = encoding == Encoding::PCM8Signed || encoding == Encoding::PCM16Signed |         auto sign = encoding == Encoding::PCM8Signed || encoding == Encoding::PCM16Signed | ||||||
|                         ? Frontend::Mic::Signedness::Signed |                         ? AudioCore::Signedness::Signed | ||||||
|                         : Frontend::Mic::Signedness::Unsigned; |                         : AudioCore::Signedness::Unsigned; | ||||||
|         mic->StartSampling({sign, state.sample_size, state.looped_buffer, |         mic->StartSampling({sign, state.sample_size, state.looped_buffer, | ||||||
|                             GetSampleRateInHz(state.sample_rate), state.initial_offset, |                             GetSampleRateInHz(state.sample_rate), state.initial_offset, | ||||||
|                             static_cast<u32>(state.size)}); |                             static_cast<u32>(state.size)}); | ||||||
|  | @ -349,21 +350,9 @@ struct MIC_U::Impl { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void CreateMic() { |     void CreateMic() { | ||||||
|         std::unique_ptr<Frontend::Mic::Interface> new_mic; |         std::unique_ptr<AudioCore::Input> new_mic = AudioCore::CreateInputFromID( | ||||||
|         switch (Settings::values.mic_input_type.GetValue()) { |             Settings::values.input_type.GetValue(), Settings::values.input_device.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>(); |  | ||||||
|         } |  | ||||||
|         // If theres already a mic, copy over any data to the new mic impl
 |         // If theres already a mic, copy over any data to the new mic impl
 | ||||||
|         if (mic) { |         if (mic) { | ||||||
|             new_mic->SetGain(mic->GetGain()); |             new_mic->SetGain(mic->GetGain()); | ||||||
|  | @ -386,7 +375,7 @@ struct MIC_U::Impl { | ||||||
|     u32 client_version = 0; |     u32 client_version = 0; | ||||||
|     bool allow_shell_closed = false; |     bool allow_shell_closed = false; | ||||||
|     bool clamp = false; |     bool clamp = false; | ||||||
|     std::unique_ptr<Frontend::Mic::Interface> mic; |     std::unique_ptr<AudioCore::Input> mic; | ||||||
|     Core::Timing& timing; |     Core::Timing& timing; | ||||||
|     State state{}; |     State state{}; | ||||||
|     Encoding encoding{}; |     Encoding encoding{}; | ||||||
|  |  | ||||||
|  | @ -26,7 +26,6 @@ | ||||||
| #include "common/settings.h" | #include "common/settings.h" | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
| #include "core/file_sys/plugin_3gx.h" | #include "core/file_sys/plugin_3gx.h" | ||||||
| #include "core/frontend/mic.h" |  | ||||||
| #include "core/hle/ipc.h" | #include "core/hle/ipc.h" | ||||||
| #include "core/hle/ipc_helpers.h" | #include "core/hle/ipc_helpers.h" | ||||||
| #include "core/hle/kernel/event.h" | #include "core/hle/kernel/event.h" | ||||||
|  |  | ||||||
|  | @ -124,7 +124,8 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { | ||||||
|     Telemetry::AppendOSInfo(field_collection); |     Telemetry::AppendOSInfo(field_collection); | ||||||
| 
 | 
 | ||||||
|     // Log user configuration information
 |     // 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", |     AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching", | ||||||
|              Settings::values.enable_audio_stretching.GetValue()); |              Settings::values.enable_audio_stretching.GetValue()); | ||||||
|     AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", |     AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue