mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	audio_core: Implement OpenAL backend (#6450)
This commit is contained in:
		
							parent
							
								
									ce553ab995
								
							
						
					
					
						commit
						055a58f01e
					
				
					 48 changed files with 1042 additions and 576 deletions
				
			
		|  | @ -20,18 +20,25 @@ add_library(audio_core STATIC | |||
|     hle/source.h | ||||
|     lle/lle.cpp | ||||
|     lle/lle.h | ||||
|     input.h | ||||
|     input_details.cpp | ||||
|     input_details.h | ||||
|     interpolate.cpp | ||||
|     interpolate.h | ||||
|     null_input.h | ||||
|     null_sink.h | ||||
|     precompiled_headers.h | ||||
|     sink.h | ||||
|     sink_details.cpp | ||||
|     sink_details.h | ||||
|     static_input.cpp | ||||
|     static_input.h | ||||
|     time_stretch.cpp | ||||
|     time_stretch.h | ||||
| 
 | ||||
|     $<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.h> | ||||
|     $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h cubeb_input.cpp cubeb_input.h> | ||||
|     $<$<BOOL:${ENABLE_OPENAL}>:openal_input.cpp openal_input.h openal_sink.cpp openal_sink.h> | ||||
| ) | ||||
| 
 | ||||
| create_target_directory_groups(audio_core) | ||||
|  | @ -92,6 +99,12 @@ if(ENABLE_CUBEB) | |||
|     target_compile_definitions(audio_core PUBLIC HAVE_CUBEB) | ||||
| endif() | ||||
| 
 | ||||
| if(ENABLE_OPENAL) | ||||
|     target_link_libraries(audio_core PRIVATE OpenAL) | ||||
|     target_compile_definitions(audio_core PUBLIC HAVE_OPENAL) | ||||
|     add_definitions(-DAL_LIBTYPE_STATIC) | ||||
| endif() | ||||
| 
 | ||||
| if (CITRA_USE_PRECOMPILED_HEADERS) | ||||
|     target_precompile_headers(audio_core PRIVATE precompiled_headers.h) | ||||
| endif() | ||||
|  |  | |||
|  | @ -6,11 +6,14 @@ | |||
| #include <vector> | ||||
| #include <cubeb/cubeb.h> | ||||
| #include "audio_core/cubeb_input.h" | ||||
| #include "audio_core/input.h" | ||||
| #include "audio_core/sink.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/threadsafe_queue.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| using SampleQueue = Common::SPSCQueue<Frontend::Mic::Samples>; | ||||
| using SampleQueue = Common::SPSCQueue<Samples>; | ||||
| 
 | ||||
| struct CubebInput::Impl { | ||||
|     cubeb* ctx = nullptr; | ||||
|  | @ -48,10 +51,10 @@ CubebInput::~CubebInput() { | |||
|     cubeb_destroy(impl->ctx); | ||||
| } | ||||
| 
 | ||||
| void CubebInput::StartSampling(const Frontend::Mic::Parameters& params) { | ||||
| void CubebInput::StartSampling(const InputParameters& params) { | ||||
|     // Cubeb apparently only supports signed 16 bit PCM (and float32 which the 3ds doesn't support)
 | ||||
|     // TODO resample the input stream
 | ||||
|     if (params.sign == Frontend::Mic::Signedness::Unsigned) { | ||||
|     if (params.sign == Signedness::Unsigned) { | ||||
|         LOG_ERROR(Audio, | ||||
|                   "Application requested unsupported unsigned pcm format. Falling back to signed"); | ||||
|     } | ||||
|  | @ -62,7 +65,7 @@ void CubebInput::StartSampling(const Frontend::Mic::Parameters& params) { | |||
|     is_sampling = true; | ||||
| 
 | ||||
|     cubeb_devid input_device = nullptr; | ||||
|     if (device_id != Frontend::Mic::default_device_name && !device_id.empty()) { | ||||
|     if (device_id != auto_device_name && !device_id.empty()) { | ||||
|         cubeb_device_collection collection; | ||||
|         if (cubeb_enumerate_devices(impl->ctx, CUBEB_DEVICE_TYPE_INPUT, &collection) != CUBEB_OK) { | ||||
|             LOG_WARNING(Audio, "Audio input device enumeration not supported"); | ||||
|  | @ -122,9 +125,9 @@ void CubebInput::AdjustSampleRate(u32 sample_rate) { | |||
|     LOG_ERROR(Audio, "AdjustSampleRate unimplemented!"); | ||||
| } | ||||
| 
 | ||||
| Frontend::Mic::Samples CubebInput::Read() { | ||||
|     Frontend::Mic::Samples samples{}; | ||||
|     Frontend::Mic::Samples queue; | ||||
| Samples CubebInput::Read() { | ||||
|     Samples samples{}; | ||||
|     Samples queue; | ||||
|     while (impl->sample_queue->Pop(queue)) { | ||||
|         samples.insert(samples.end(), queue.begin(), queue.end()); | ||||
|     } | ||||
|  | @ -190,10 +193,4 @@ std::vector<std::string> ListCubebInputDevices() { | |||
|     return device_list; | ||||
| } | ||||
| 
 | ||||
| CubebFactory::~CubebFactory() = default; | ||||
| 
 | ||||
| std::unique_ptr<Frontend::Mic::Interface> CubebFactory::Create(std::string mic_device_name) { | ||||
|     return std::make_unique<CubebInput>(std::move(mic_device_name)); | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -5,23 +5,24 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include "core/frontend/mic.h" | ||||
| #include "audio_core/input.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| class CubebInput final : public Frontend::Mic::Interface { | ||||
| class CubebInput final : public Input { | ||||
| public: | ||||
|     explicit CubebInput(std::string device_id); | ||||
|     ~CubebInput() override; | ||||
| 
 | ||||
|     void StartSampling(const Frontend::Mic::Parameters& params) override; | ||||
|     void StartSampling(const InputParameters& params) override; | ||||
| 
 | ||||
|     void StopSampling() override; | ||||
| 
 | ||||
|     void AdjustSampleRate(u32 sample_rate) override; | ||||
| 
 | ||||
|     Frontend::Mic::Samples Read() override; | ||||
|     Samples Read() override; | ||||
| 
 | ||||
| private: | ||||
|     struct Impl; | ||||
|  | @ -31,11 +32,4 @@ private: | |||
| 
 | ||||
| std::vector<std::string> ListCubebInputDevices(); | ||||
| 
 | ||||
| class CubebFactory final : public Frontend::Mic::RealMicFactory { | ||||
| public: | ||||
|     ~CubebFactory() override; | ||||
| 
 | ||||
|     std::unique_ptr<Frontend::Mic::Interface> Create(std::string mic_device_name) override; | ||||
| }; | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| 
 | ||||
| #include <cstddef> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include "audio_core/sink.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
|  |  | |||
|  | @ -16,9 +16,8 @@ namespace AudioCore { | |||
| DspInterface::DspInterface() = default; | ||||
| DspInterface::~DspInterface() = default; | ||||
| 
 | ||||
| void DspInterface::SetSink(std::string_view sink_id, std::string_view audio_device) { | ||||
|     sink = CreateSinkFromID(Settings::values.sink_id.GetValue(), | ||||
|                             Settings::values.audio_device_id.GetValue()); | ||||
| void DspInterface::SetSink(AudioCore::SinkType sink_type, std::string_view audio_device) { | ||||
|     sink = CreateSinkFromID(sink_type, audio_device); | ||||
|     sink->SetCallback( | ||||
|         [this](s16* buffer, std::size_t num_frames) { OutputCallback(buffer, num_frames); }); | ||||
|     time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ class DSP_DSP; | |||
| namespace AudioCore { | ||||
| 
 | ||||
| class Sink; | ||||
| enum class SinkType : u32; | ||||
| 
 | ||||
| class DspInterface { | ||||
| public: | ||||
|  | @ -93,8 +94,8 @@ public: | |||
|     /// Unloads the DSP program
 | ||||
|     virtual void UnloadComponent() = 0; | ||||
| 
 | ||||
|     /// Select the sink to use based on sink id.
 | ||||
|     void SetSink(std::string_view sink_id, std::string_view audio_device); | ||||
|     /// Select the sink to use based on sink type.
 | ||||
|     void SetSink(SinkType sink_type, std::string_view audio_device); | ||||
|     /// Get the current sink
 | ||||
|     Sink& GetSink(); | ||||
|     /// Enable/Disable audio stretching.
 | ||||
|  |  | |||
							
								
								
									
										85
									
								
								src/audio_core/input.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/audio_core/input.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,85 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| enum class Signedness : u8 { | ||||
|     Signed, | ||||
|     Unsigned, | ||||
| }; | ||||
| 
 | ||||
| using Samples = std::vector<u8>; | ||||
| 
 | ||||
| struct InputParameters { | ||||
|     Signedness sign; | ||||
|     u8 sample_size; | ||||
|     bool buffer_loop; | ||||
|     u32 sample_rate; | ||||
|     u32 buffer_offset; | ||||
|     u32 buffer_size; | ||||
| }; | ||||
| 
 | ||||
| class Input { | ||||
| public: | ||||
|     Input() = default; | ||||
| 
 | ||||
|     virtual ~Input() = default; | ||||
| 
 | ||||
|     /// Starts the microphone. Called by Core
 | ||||
|     virtual void StartSampling(const InputParameters& params) = 0; | ||||
| 
 | ||||
|     /// Stops the microphone. Called by Core
 | ||||
|     virtual void StopSampling() = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Called from the actual event timing at a constant period under a given sample rate. | ||||
|      * When sampling is enabled this function is expected to return a buffer of 16 samples in ideal | ||||
|      * conditions, but can be lax if the data is coming in from another source like a real mic. | ||||
|      */ | ||||
|     virtual Samples Read() = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Adjusts the Parameters. Implementations should update the parameters field in addition to | ||||
|      * changing the mic to sample according to the new parameters. Called by Core | ||||
|      */ | ||||
|     virtual void AdjustSampleRate(u32 sample_rate) = 0; | ||||
| 
 | ||||
|     /// Value from 0 - 100 to adjust the mic gain setting. Called by Core
 | ||||
|     virtual void SetGain(u8 mic_gain) { | ||||
|         gain = mic_gain; | ||||
|     } | ||||
| 
 | ||||
|     u8 GetGain() const { | ||||
|         return gain; | ||||
|     } | ||||
| 
 | ||||
|     void SetPower(bool power) { | ||||
|         powered = power; | ||||
|     } | ||||
| 
 | ||||
|     bool GetPower() const { | ||||
|         return powered; | ||||
|     } | ||||
| 
 | ||||
|     bool IsSampling() const { | ||||
|         return is_sampling; | ||||
|     } | ||||
| 
 | ||||
|     const InputParameters& GetParameters() const { | ||||
|         return parameters; | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     InputParameters parameters; | ||||
|     u8 gain = 0; | ||||
|     bool is_sampling = false; | ||||
|     bool powered = false; | ||||
| }; | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										108
									
								
								src/audio_core/input_details.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/audio_core/input_details.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,108 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include "audio_core/input_details.h" | ||||
| #include "audio_core/null_input.h" | ||||
| #include "audio_core/static_input.h" | ||||
| #ifdef HAVE_CUBEB | ||||
| #include "audio_core/cubeb_input.h" | ||||
| #endif | ||||
| #ifdef HAVE_OPENAL | ||||
| #include "audio_core/openal_input.h" | ||||
| #endif | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| namespace { | ||||
| struct InputDetails { | ||||
|     using FactoryFn = std::unique_ptr<Input> (*)(std::string_view); | ||||
|     using ListDevicesFn = std::vector<std::string> (*)(); | ||||
| 
 | ||||
|     /// Type of this input.
 | ||||
|     InputType type; | ||||
|     /// Name for this input.
 | ||||
|     std::string_view name; | ||||
|     /// A method to call to construct an instance of this type of input.
 | ||||
|     FactoryFn factory; | ||||
|     /// A method to call to list available devices.
 | ||||
|     ListDevicesFn list_devices; | ||||
| }; | ||||
| 
 | ||||
| // input_details is ordered in terms of desirability, with the best choice at the top.
 | ||||
| constexpr std::array input_details = { | ||||
| #ifdef HAVE_CUBEB | ||||
|     InputDetails{InputType::Cubeb, "Real Device (Cubeb)", | ||||
|                  [](std::string_view device_id) -> std::unique_ptr<Input> { | ||||
|                      if (!Core::System::GetInstance().HasMicPermission()) { | ||||
|                          LOG_WARNING(Audio, | ||||
|                                      "Microphone permission denied, falling back to null input."); | ||||
|                          return std::make_unique<NullInput>(); | ||||
|                      } | ||||
|                      return std::make_unique<CubebInput>(std::string(device_id)); | ||||
|                  }, | ||||
|                  &ListCubebInputDevices}, | ||||
| #endif | ||||
| #ifdef HAVE_OPENAL | ||||
|     InputDetails{InputType::OpenAL, "Real Device (OpenAL)", | ||||
|                  [](std::string_view device_id) -> std::unique_ptr<Input> { | ||||
|                      if (!Core::System::GetInstance().HasMicPermission()) { | ||||
|                          LOG_WARNING(Audio, | ||||
|                                      "Microphone permission denied, falling back to null input."); | ||||
|                          return std::make_unique<NullInput>(); | ||||
|                      } | ||||
|                      return std::make_unique<OpenALInput>(std::string(device_id)); | ||||
|                  }, | ||||
|                  &ListOpenALInputDevices}, | ||||
| #endif | ||||
|     InputDetails{InputType::Static, "Static Noise", | ||||
|                  [](std::string_view device_id) -> std::unique_ptr<Input> { | ||||
|                      return std::make_unique<StaticInput>(); | ||||
|                  }, | ||||
|                  [] { return std::vector<std::string>{"Static Noise"}; }}, | ||||
|     InputDetails{InputType::Null, "None", | ||||
|                  [](std::string_view device_id) -> std::unique_ptr<Input> { | ||||
|                      return std::make_unique<NullInput>(); | ||||
|                  }, | ||||
|                  [] { return std::vector<std::string>{"None"}; }}, | ||||
| }; | ||||
| 
 | ||||
| const InputDetails& GetInputDetails(InputType input_type) { | ||||
|     auto iter = std::find_if( | ||||
|         input_details.begin(), input_details.end(), | ||||
|         [input_type](const auto& input_detail) { return input_detail.type == input_type; }); | ||||
| 
 | ||||
|     if (input_type == InputType::Auto || iter == input_details.end()) { | ||||
|         if (input_type != InputType::Auto) { | ||||
|             LOG_ERROR(Audio, "AudioCore::GetInputDetails given invalid input_type {}", input_type); | ||||
|         } | ||||
|         // Auto-select.
 | ||||
|         // input_details is ordered in terms of desirability, with the best choice at the front.
 | ||||
|         iter = input_details.begin(); | ||||
|     } | ||||
| 
 | ||||
|     return *iter; | ||||
| } | ||||
| } // Anonymous namespace
 | ||||
| 
 | ||||
| std::string_view GetInputName(InputType input_type) { | ||||
|     if (input_type == InputType::Auto) { | ||||
|         return "Auto"; | ||||
|     } | ||||
|     return GetInputDetails(input_type).name; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> GetDeviceListForInput(InputType input_type) { | ||||
|     return GetInputDetails(input_type).list_devices(); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<Input> CreateInputFromID(InputType input_type, std::string_view device_id) { | ||||
|     return GetInputDetails(input_type).factory(device_id); | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										36
									
								
								src/audio_core/input_details.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/audio_core/input_details.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <string_view> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| class Input; | ||||
| 
 | ||||
| enum class InputType : u32 { | ||||
|     Auto = 0, | ||||
|     Null = 1, | ||||
|     Static = 2, | ||||
|     Cubeb = 3, | ||||
|     OpenAL = 4, | ||||
| 
 | ||||
|     NumInputTypes, | ||||
| }; | ||||
| 
 | ||||
| /// Gets the name of a input type.
 | ||||
| std::string_view GetInputName(InputType input_type); | ||||
| 
 | ||||
| /// Gets the list of devices for a particular input identified by the given ID.
 | ||||
| std::vector<std::string> GetDeviceListForInput(InputType input_type); | ||||
| 
 | ||||
| /// Creates an audio input identified by the given device ID.
 | ||||
| std::unique_ptr<Input> CreateInputFromID(InputType input_type, std::string_view device_id); | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										35
									
								
								src/audio_core/null_input.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/audio_core/null_input.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| // Copyright 2019 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include "audio_core/input.h" | ||||
| #include "common/swap.h" | ||||
| #include "common/threadsafe_queue.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| class NullInput final : public Input { | ||||
| public: | ||||
|     void StartSampling(const InputParameters& params) override { | ||||
|         parameters = params; | ||||
|         is_sampling = true; | ||||
|     } | ||||
| 
 | ||||
|     void StopSampling() override { | ||||
|         is_sampling = false; | ||||
|     } | ||||
| 
 | ||||
|     void AdjustSampleRate(u32 sample_rate) override { | ||||
|         parameters.sample_rate = sample_rate; | ||||
|     } | ||||
| 
 | ||||
|     Samples Read() override { | ||||
|         return {}; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										133
									
								
								src/audio_core/openal_input.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/audio_core/openal_input.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,133 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| #include <AL/al.h> | ||||
| #include <AL/alc.h> | ||||
| #include "audio_core/input.h" | ||||
| #include "audio_core/openal_input.h" | ||||
| #include "audio_core/sink.h" | ||||
| #include "common/logging/log.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| struct OpenALInput::Impl { | ||||
|     ALCdevice* device = nullptr; | ||||
|     u8 sample_size_in_bytes = 0; | ||||
| }; | ||||
| 
 | ||||
| OpenALInput::OpenALInput(std::string device_id) | ||||
|     : impl(std::make_unique<Impl>()), device_id(std::move(device_id)) {} | ||||
| 
 | ||||
| OpenALInput::~OpenALInput() { | ||||
|     StopSampling(); | ||||
| } | ||||
| 
 | ||||
| void OpenALInput::StartSampling(const InputParameters& params) { | ||||
|     if (is_sampling) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // OpenAL supports unsigned 8-bit and signed 16-bit PCM.
 | ||||
|     // TODO: Re-sample the stream.
 | ||||
|     if ((params.sample_size == 8 && params.sign == Signedness::Signed) || | ||||
|         (params.sample_size == 16 && params.sign == Signedness::Unsigned)) { | ||||
|         LOG_WARNING(Audio, "Application requested unsupported unsigned PCM format. Falling back to " | ||||
|                            "supported format."); | ||||
|     } | ||||
| 
 | ||||
|     parameters = params; | ||||
|     impl->sample_size_in_bytes = params.sample_size / 8; | ||||
| 
 | ||||
|     auto format = params.sample_size == 16 ? AL_FORMAT_MONO16 : AL_FORMAT_MONO8; | ||||
|     impl->device = alcCaptureOpenDevice( | ||||
|         device_id != auto_device_name && !device_id.empty() ? device_id.c_str() : nullptr, | ||||
|         params.sample_rate, format, static_cast<ALsizei>(params.buffer_size)); | ||||
|     if (!impl->device) { | ||||
|         LOG_CRITICAL(Audio, "alcCaptureOpenDevice failed."); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     alcCaptureStart(impl->device); | ||||
|     auto error = alcGetError(impl->device); | ||||
|     if (error != ALC_NO_ERROR) { | ||||
|         LOG_CRITICAL(Audio, "alcCaptureStart failed: {}", error); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     is_sampling = true; | ||||
| } | ||||
| 
 | ||||
| void OpenALInput::StopSampling() { | ||||
|     if (impl->device) { | ||||
|         alcCaptureStop(impl->device); | ||||
|         alcCaptureCloseDevice(impl->device); | ||||
|         impl->device = nullptr; | ||||
|     } | ||||
|     is_sampling = false; | ||||
| } | ||||
| 
 | ||||
| void OpenALInput::AdjustSampleRate(u32 sample_rate) { | ||||
|     if (!is_sampling) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     auto new_params = parameters; | ||||
|     new_params.sample_rate = sample_rate; | ||||
|     StopSampling(); | ||||
|     StartSampling(new_params); | ||||
| } | ||||
| 
 | ||||
| Samples OpenALInput::Read() { | ||||
|     if (!is_sampling) { | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     ALCint samples_captured = 0; | ||||
|     alcGetIntegerv(impl->device, ALC_CAPTURE_SAMPLES, 1, &samples_captured); | ||||
|     auto error = alcGetError(impl->device); | ||||
|     if (error != ALC_NO_ERROR) { | ||||
|         LOG_WARNING(Audio, "alcGetIntegerv(ALC_CAPTURE_SAMPLES) failed: {}", error); | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     auto num_samples = std::min(samples_captured, static_cast<ALsizei>(parameters.buffer_size / | ||||
|                                                                        impl->sample_size_in_bytes)); | ||||
|     Samples samples(num_samples * impl->sample_size_in_bytes); | ||||
| 
 | ||||
|     alcCaptureSamples(impl->device, samples.data(), num_samples); | ||||
|     error = alcGetError(impl->device); | ||||
|     if (error != ALC_NO_ERROR) { | ||||
|         LOG_WARNING(Audio, "alcCaptureSamples failed: {}", error); | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     return samples; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> ListOpenALInputDevices() { | ||||
|     const char* devices_str; | ||||
|     if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT") != AL_FALSE) { | ||||
|         devices_str = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); | ||||
|     } else { | ||||
|         LOG_WARNING( | ||||
|             Audio, | ||||
|             "Missing OpenAL device enumeration extensions, cannot list audio capture devices."); | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     if (!devices_str || *devices_str == '\0') { | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<std::string> device_list; | ||||
|     while (*devices_str != '\0') { | ||||
|         device_list.emplace_back(devices_str); | ||||
|         devices_str += strlen(devices_str) + 1; | ||||
|     } | ||||
|     return device_list; | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										35
									
								
								src/audio_core/openal_input.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/audio_core/openal_input.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include "audio_core/input.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| class OpenALInput final : public Input { | ||||
| public: | ||||
|     explicit OpenALInput(std::string device_id); | ||||
|     ~OpenALInput() override; | ||||
| 
 | ||||
|     void StartSampling(const InputParameters& params) override; | ||||
| 
 | ||||
|     void StopSampling() override; | ||||
| 
 | ||||
|     void AdjustSampleRate(u32 sample_rate) override; | ||||
| 
 | ||||
|     Samples Read() override; | ||||
| 
 | ||||
| private: | ||||
|     struct Impl; | ||||
|     std::unique_ptr<Impl> impl; | ||||
|     std::string device_id; | ||||
| }; | ||||
| 
 | ||||
| std::vector<std::string> ListOpenALInputDevices(); | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										176
									
								
								src/audio_core/openal_sink.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								src/audio_core/openal_sink.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,176 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <AL/al.h> | ||||
| #include <AL/alc.h> | ||||
| #include <AL/alext.h> | ||||
| #include "audio_core/audio_types.h" | ||||
| #include "audio_core/openal_sink.h" | ||||
| #include "common/logging/log.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| struct OpenALSink::Impl { | ||||
|     unsigned int sample_rate = 0; | ||||
| 
 | ||||
|     ALCdevice* device = nullptr; | ||||
|     ALCcontext* context = nullptr; | ||||
|     ALuint buffer = 0; | ||||
|     ALuint source = 0; | ||||
| 
 | ||||
|     std::function<void(s16*, std::size_t)> cb; | ||||
| 
 | ||||
|     static ALsizei Callback(void* impl_, void* buffer, ALsizei buffer_size_in_bytes); | ||||
| }; | ||||
| 
 | ||||
| OpenALSink::OpenALSink(std::string device_name) : impl(std::make_unique<Impl>()) { | ||||
|     impl->device = alcOpenDevice( | ||||
|         device_name != auto_device_name && !device_name.empty() ? device_name.c_str() : nullptr); | ||||
|     if (!impl->device) { | ||||
|         LOG_CRITICAL(Audio_Sink, "alcOpenDevice failed."); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     impl->context = alcCreateContext(impl->device, nullptr); | ||||
|     if (impl->context == nullptr) { | ||||
|         LOG_CRITICAL(Audio_Sink, "alcCreateContext failed: {}", alcGetError(impl->device)); | ||||
|         alcCloseDevice(impl->device); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (alcMakeContextCurrent(impl->context) == ALC_FALSE) { | ||||
|         LOG_CRITICAL(Audio_Sink, "alcMakeContextCurrent failed: {}", alcGetError(impl->device)); | ||||
|         alcDestroyContext(impl->context); | ||||
|         alcCloseDevice(impl->device); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (alIsExtensionPresent("AL_SOFT_callback_buffer") == AL_FALSE) { | ||||
|         if (alGetError() != AL_NO_ERROR) { | ||||
|             LOG_CRITICAL(Audio_Sink, "alIsExtensionPresent failed: {}", alGetError()); | ||||
|         } else { | ||||
|             LOG_CRITICAL(Audio_Sink, "Missing required extension AL_SOFT_callback_buffer."); | ||||
|         } | ||||
|         alcDestroyContext(impl->context); | ||||
|         alcCloseDevice(impl->device); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     alGenBuffers(1, &impl->buffer); | ||||
|     if (alGetError() != AL_NO_ERROR) { | ||||
|         LOG_CRITICAL(Audio_Sink, "alGetError failed: {}", alGetError()); | ||||
|         alcDestroyContext(impl->context); | ||||
|         alcCloseDevice(impl->device); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     alGenSources(1, &impl->source); | ||||
|     if (alGetError() != AL_NO_ERROR) { | ||||
|         LOG_CRITICAL(Audio_Sink, "alGenSources failed: {}", alGetError()); | ||||
|         alDeleteBuffers(1, &impl->buffer); | ||||
|         alcDestroyContext(impl->context); | ||||
|         alcCloseDevice(impl->device); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     auto alBufferCallbackSOFT = | ||||
|         reinterpret_cast<LPALBUFFERCALLBACKSOFT>(alGetProcAddress("alBufferCallbackSOFT")); | ||||
|     alBufferCallbackSOFT(impl->buffer, AL_FORMAT_STEREO16, native_sample_rate, &Impl::Callback, | ||||
|                          impl.get()); | ||||
|     if (alGetError() != AL_NO_ERROR) { | ||||
|         LOG_CRITICAL(Audio_Sink, "alBufferCallbackSOFT failed: {}", alGetError()); | ||||
|         alDeleteSources(1, &impl->source); | ||||
|         alDeleteBuffers(1, &impl->buffer); | ||||
|         alcDestroyContext(impl->context); | ||||
|         alcCloseDevice(impl->device); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     alSourcei(impl->source, AL_BUFFER, static_cast<ALint>(impl->buffer)); | ||||
|     if (alGetError() != AL_NO_ERROR) { | ||||
|         LOG_CRITICAL(Audio_Sink, "alSourcei failed: {}", alGetError()); | ||||
|         alDeleteSources(1, &impl->source); | ||||
|         alDeleteBuffers(1, &impl->buffer); | ||||
|         alcDestroyContext(impl->context); | ||||
|         alcCloseDevice(impl->device); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     alSourcePlay(impl->source); | ||||
|     if (alGetError() != AL_NO_ERROR) { | ||||
|         LOG_CRITICAL(Audio_Sink, "alSourcePlay failed: {}", alGetError()); | ||||
|         alDeleteSources(1, &impl->source); | ||||
|         alDeleteBuffers(1, &impl->buffer); | ||||
|         alcDestroyContext(impl->context); | ||||
|         alcCloseDevice(impl->device); | ||||
|         return; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| OpenALSink::~OpenALSink() { | ||||
|     if (impl->source) { | ||||
|         alSourceStop(impl->source); | ||||
|         alDeleteSources(1, &impl->source); | ||||
|         impl->source = 0; | ||||
|     } | ||||
|     if (impl->buffer) { | ||||
|         alDeleteBuffers(1, &impl->buffer); | ||||
|         impl->buffer = 0; | ||||
|     } | ||||
|     if (impl->context) { | ||||
|         alcDestroyContext(impl->context); | ||||
|         impl->context = nullptr; | ||||
|     } | ||||
|     if (impl->device) { | ||||
|         alcCloseDevice(impl->device); | ||||
|         impl->device = nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| unsigned int OpenALSink::GetNativeSampleRate() const { | ||||
|     return native_sample_rate; | ||||
| } | ||||
| 
 | ||||
| void OpenALSink::SetCallback(std::function<void(s16*, std::size_t)> cb) { | ||||
|     impl->cb = cb; | ||||
| } | ||||
| 
 | ||||
| ALsizei OpenALSink::Impl::Callback(void* impl_, void* buffer, ALsizei buffer_size_in_bytes) { | ||||
|     auto impl = reinterpret_cast<Impl*>(impl_); | ||||
|     if (!impl || !impl->cb) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     const size_t num_frames = buffer_size_in_bytes / (2 * sizeof(s16)); | ||||
|     impl->cb(reinterpret_cast<s16*>(buffer), num_frames); | ||||
| 
 | ||||
|     return buffer_size_in_bytes; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> ListOpenALSinkDevices() { | ||||
|     const char* devices_str; | ||||
|     if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") != AL_FALSE) { | ||||
|         devices_str = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); | ||||
|     } else if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT") != AL_FALSE) { | ||||
|         devices_str = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); | ||||
|     } else { | ||||
|         LOG_WARNING(Audio_Sink, | ||||
|                     "Missing OpenAL device enumeration extensions, cannot list audio devices."); | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     if (!devices_str || *devices_str == '\0') { | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<std::string> device_list; | ||||
|     while (*devices_str != '\0') { | ||||
|         device_list.emplace_back(devices_str); | ||||
|         devices_str += strlen(devices_str) + 1; | ||||
|     } | ||||
|     return device_list; | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										30
									
								
								src/audio_core/openal_sink.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/audio_core/openal_sink.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include "audio_core/sink.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| class OpenALSink final : public Sink { | ||||
| public: | ||||
|     explicit OpenALSink(std::string device_id); | ||||
|     ~OpenALSink() override; | ||||
| 
 | ||||
|     unsigned int GetNativeSampleRate() const override; | ||||
| 
 | ||||
|     void SetCallback(std::function<void(s16*, std::size_t)> cb) override; | ||||
| 
 | ||||
| private: | ||||
|     struct Impl; | ||||
|     std::unique_ptr<Impl> impl; | ||||
| }; | ||||
| 
 | ||||
| std::vector<std::string> ListOpenALSinkDevices(); | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  | @ -14,6 +14,9 @@ | |||
| #ifdef HAVE_CUBEB | ||||
| #include "audio_core/cubeb_sink.h" | ||||
| #endif | ||||
| #ifdef HAVE_OPENAL | ||||
| #include "audio_core/openal_sink.h" | ||||
| #endif | ||||
| #include "common/logging/log.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
|  | @ -22,8 +25,10 @@ struct SinkDetails { | |||
|     using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); | ||||
|     using ListDevicesFn = std::vector<std::string> (*)(); | ||||
| 
 | ||||
|     /// Type of this sink.
 | ||||
|     SinkType type; | ||||
|     /// Name for this sink.
 | ||||
|     const char* id; | ||||
|     std::string_view name; | ||||
|     /// A method to call to construct an instance of this type of sink.
 | ||||
|     FactoryFn factory; | ||||
|     /// A method to call to list available devices.
 | ||||
|  | @ -31,61 +36,66 @@ struct SinkDetails { | |||
| }; | ||||
| 
 | ||||
| // sink_details is ordered in terms of desirability, with the best choice at the top.
 | ||||
| constexpr SinkDetails sink_details[] = { | ||||
| constexpr std::array sink_details = { | ||||
| #ifdef HAVE_CUBEB | ||||
|     SinkDetails{"cubeb", | ||||
|     SinkDetails{SinkType::Cubeb, "Cubeb", | ||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||
|                     return std::make_unique<CubebSink>(device_id); | ||||
|                 }, | ||||
|                 &ListCubebSinkDevices}, | ||||
| #endif | ||||
| #ifdef HAVE_OPENAL | ||||
|     SinkDetails{SinkType::OpenAL, "OpenAL", | ||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||
|                     return std::make_unique<OpenALSink>(std::string(device_id)); | ||||
|                 }, | ||||
|                 &ListOpenALSinkDevices}, | ||||
| #endif | ||||
| #ifdef HAVE_SDL2 | ||||
|     SinkDetails{"sdl2", | ||||
|     SinkDetails{SinkType::SDL2, "SDL2", | ||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||
|                     return std::make_unique<SDL2Sink>(std::string(device_id)); | ||||
|                 }, | ||||
|                 &ListSDL2SinkDevices}, | ||||
| #endif | ||||
|     SinkDetails{"null", | ||||
|     SinkDetails{SinkType::Null, "None", | ||||
|                 [](std::string_view device_id) -> std::unique_ptr<Sink> { | ||||
|                     return std::make_unique<NullSink>(device_id); | ||||
|                 }, | ||||
|                 [] { return std::vector<std::string>{"null"}; }}, | ||||
|                 [] { return std::vector<std::string>{"None"}; }}, | ||||
| }; | ||||
| 
 | ||||
| const SinkDetails& GetSinkDetails(std::string_view sink_id) { | ||||
|     auto iter = | ||||
|         std::find_if(std::begin(sink_details), std::end(sink_details), | ||||
|                      [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); | ||||
| const SinkDetails& GetSinkDetails(SinkType sink_type) { | ||||
|     auto iter = std::find_if( | ||||
|         sink_details.begin(), sink_details.end(), | ||||
|         [sink_type](const auto& sink_detail) { return sink_detail.type == sink_type; }); | ||||
| 
 | ||||
|     if (sink_id == "auto" || iter == std::end(sink_details)) { | ||||
|         if (sink_id != "auto") { | ||||
|             LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id); | ||||
|     if (sink_type == SinkType::Auto || iter == sink_details.end()) { | ||||
|         if (sink_type != SinkType::Auto) { | ||||
|             LOG_ERROR(Audio, "AudioCore::GetSinkDetails given invalid sink_type {}", sink_type); | ||||
|         } | ||||
|         // Auto-select.
 | ||||
|         // sink_details is ordered in terms of desirability, with the best choice at the front.
 | ||||
|         iter = std::begin(sink_details); | ||||
|         iter = sink_details.begin(); | ||||
|     } | ||||
| 
 | ||||
|     return *iter; | ||||
| } | ||||
| } // Anonymous namespace
 | ||||
| 
 | ||||
| std::vector<const char*> GetSinkIDs() { | ||||
|     std::vector<const char*> sink_ids(std::size(sink_details)); | ||||
| 
 | ||||
|     std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids), | ||||
|                    [](const auto& sink) { return sink.id; }); | ||||
| 
 | ||||
|     return sink_ids; | ||||
| std::string_view GetSinkName(SinkType sink_type) { | ||||
|     if (sink_type == SinkType::Auto) { | ||||
|         return "Auto"; | ||||
|     } | ||||
|     return GetSinkDetails(sink_type).name; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) { | ||||
|     return GetSinkDetails(sink_id).list_devices(); | ||||
| std::vector<std::string> GetDeviceListForSink(SinkType sink_type) { | ||||
|     return GetSinkDetails(sink_type).list_devices(); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) { | ||||
|     return GetSinkDetails(sink_id).factory(device_id); | ||||
| std::unique_ptr<Sink> CreateSinkFromID(SinkType sink_type, std::string_view device_id) { | ||||
|     return GetSinkDetails(sink_type).factory(device_id); | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
|  | @ -4,21 +4,33 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <string_view> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| class Sink; | ||||
| 
 | ||||
| /// Retrieves the IDs for all available audio sinks.
 | ||||
| std::vector<const char*> GetSinkIDs(); | ||||
| enum class SinkType : u32 { | ||||
|     Auto = 0, | ||||
|     Null = 1, | ||||
|     Cubeb = 2, | ||||
|     OpenAL = 3, | ||||
|     SDL2 = 4, | ||||
| 
 | ||||
|     NumSinkTypes, | ||||
| }; | ||||
| 
 | ||||
| /// Gets the name of a sink type.
 | ||||
| std::string_view GetSinkName(SinkType sink_type); | ||||
| 
 | ||||
| /// Gets the list of devices for a particular sink identified by the given ID.
 | ||||
| std::vector<std::string> GetDeviceListForSink(std::string_view sink_id); | ||||
| std::vector<std::string> GetDeviceListForSink(SinkType sink_type); | ||||
| 
 | ||||
| /// Creates an audio sink identified by the given device ID.
 | ||||
| std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id); | ||||
| std::unique_ptr<Sink> CreateSinkFromID(SinkType sink_type, std::string_view device_id); | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
|  |  | |||
							
								
								
									
										42
									
								
								src/audio_core/static_input.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/audio_core/static_input.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| // Copyright 2019 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <array> | ||||
| #include "audio_core/input.h" | ||||
| #include "audio_core/static_input.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| constexpr std::array<u8, 16> NOISE_SAMPLE_8_BIT = {0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|                                                    0xFF, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF}; | ||||
| 
 | ||||
| constexpr std::array<u8, 32> NOISE_SAMPLE_16_BIT = { | ||||
|     0x64, 0x61, 0x74, 0x61, 0x56, 0xD7, 0x00, 0x00, 0x48, 0xF7, 0x86, 0x05, 0x77, 0x1A, 0xF4, 0x1F, | ||||
|     0x28, 0x0F, 0x6B, 0xEB, 0x1C, 0xC0, 0xCB, 0x9D, 0x46, 0x90, 0xDF, 0x98, 0xEA, 0xAE, 0xB5, 0xC4}; | ||||
| 
 | ||||
| StaticInput::StaticInput() | ||||
|     : CACHE_8_BIT{NOISE_SAMPLE_8_BIT.begin(), NOISE_SAMPLE_8_BIT.end()}, | ||||
|       CACHE_16_BIT{NOISE_SAMPLE_16_BIT.begin(), NOISE_SAMPLE_16_BIT.end()} {} | ||||
| 
 | ||||
| StaticInput::~StaticInput() = default; | ||||
| 
 | ||||
| void StaticInput::StartSampling(const InputParameters& params) { | ||||
|     sample_rate = params.sample_rate; | ||||
|     sample_size = params.sample_size; | ||||
| 
 | ||||
|     parameters = params; | ||||
|     is_sampling = true; | ||||
| } | ||||
| 
 | ||||
| void StaticInput::StopSampling() { | ||||
|     is_sampling = false; | ||||
| } | ||||
| 
 | ||||
| void StaticInput::AdjustSampleRate(u32 sample_rate) {} | ||||
| 
 | ||||
| Samples StaticInput::Read() { | ||||
|     return (sample_size == 8) ? CACHE_8_BIT : CACHE_16_BIT; | ||||
| } | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
							
								
								
									
										33
									
								
								src/audio_core/static_input.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/audio_core/static_input.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| // Copyright 2019 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include "audio_core/input.h" | ||||
| #include "common/swap.h" | ||||
| #include "common/threadsafe_queue.h" | ||||
| 
 | ||||
| namespace AudioCore { | ||||
| 
 | ||||
| class StaticInput final : public Input { | ||||
| public: | ||||
|     StaticInput(); | ||||
|     ~StaticInput() override; | ||||
| 
 | ||||
|     void StartSampling(const InputParameters& params) override; | ||||
|     void StopSampling() override; | ||||
|     void AdjustSampleRate(u32 sample_rate) override; | ||||
| 
 | ||||
|     Samples Read() override; | ||||
| 
 | ||||
| private: | ||||
|     u16 sample_rate = 0; | ||||
|     u8 sample_size = 0; | ||||
|     std::vector<u8> CACHE_8_BIT; | ||||
|     std::vector<u8> CACHE_16_BIT; | ||||
| }; | ||||
| 
 | ||||
| } // namespace AudioCore
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue