AudioCore: SDL2 Sink
This commit is contained in:
		| @@ -152,12 +152,15 @@ if (ENABLE_SDL2) | |||||||
|             download_bundled_external("sdl2/" ${SDL2_VER} SDL2_PREFIX) |             download_bundled_external("sdl2/" ${SDL2_VER} SDL2_PREFIX) | ||||||
|         endif() |         endif() | ||||||
|  |  | ||||||
|  |         set(SDL2_FOUND YES) | ||||||
|         set(SDL2_INCLUDE_DIR "${SDL2_PREFIX}/include" CACHE PATH "Path to SDL2 headers") |         set(SDL2_INCLUDE_DIR "${SDL2_PREFIX}/include" CACHE PATH "Path to SDL2 headers") | ||||||
|         set(SDL2_LIBRARY "${SDL2_PREFIX}/lib/x64/SDL2.lib" CACHE PATH "Path to SDL2 library") |         set(SDL2_LIBRARY "${SDL2_PREFIX}/lib/x64/SDL2.lib" CACHE PATH "Path to SDL2 library") | ||||||
|         set(SDL2_DLL_DIR "${SDL2_PREFIX}/lib/x64/" CACHE PATH "Path to SDL2.dll") |         set(SDL2_DLL_DIR "${SDL2_PREFIX}/lib/x64/" CACHE PATH "Path to SDL2.dll") | ||||||
|     else() |     else() | ||||||
|         find_package(SDL2 REQUIRED) |         find_package(SDL2 REQUIRED) | ||||||
|     endif() |     endif() | ||||||
|  | else() | ||||||
|  |     set(SDL2_FOUND NO) | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
| IF (APPLE) | IF (APPLE) | ||||||
|   | |||||||
| @@ -23,7 +23,18 @@ set(HEADERS | |||||||
|  |  | ||||||
| include_directories(../../externals/soundtouch/include) | include_directories(../../externals/soundtouch/include) | ||||||
|  |  | ||||||
|  | if(SDL2_FOUND) | ||||||
|  |     set(SRCS ${SRCS} sdl2_sink.cpp) | ||||||
|  |     set(HEADERS ${HEADERS} sdl2_sink.h) | ||||||
|  |     include_directories(${SDL2_INCLUDE_DIR}) | ||||||
|  | endif() | ||||||
|  |  | ||||||
| create_directory_groups(${SRCS} ${HEADERS}) | create_directory_groups(${SRCS} ${HEADERS}) | ||||||
|  |  | ||||||
| add_library(audio_core STATIC ${SRCS} ${HEADERS}) | add_library(audio_core STATIC ${SRCS} ${HEADERS}) | ||||||
| target_link_libraries(audio_core SoundTouch) | target_link_libraries(audio_core SoundTouch) | ||||||
|  |  | ||||||
|  | if(SDL2_FOUND) | ||||||
|  |     target_link_libraries(audio_core ${SDL2_LIBRARY}) | ||||||
|  |     set_property(TARGET audio_core APPEND PROPERTY COMPILE_DEFINITIONS HAVE_SDL2) | ||||||
|  | endif() | ||||||
|   | |||||||
							
								
								
									
										126
									
								
								src/audio_core/sdl2_sink.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/audio_core/sdl2_sink.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | |||||||
|  | // Copyright 2016 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <list> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | #include <SDL.h> | ||||||
|  |  | ||||||
|  | #include "audio_core/audio_core.h" | ||||||
|  | #include "audio_core/sdl2_sink.h" | ||||||
|  |  | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include <numeric> | ||||||
|  |  | ||||||
|  | namespace AudioCore { | ||||||
|  |  | ||||||
|  | struct SDL2Sink::Impl { | ||||||
|  |     unsigned int sample_rate = 0; | ||||||
|  |  | ||||||
|  |     SDL_AudioDeviceID audio_device_id = 0; | ||||||
|  |  | ||||||
|  |     std::list<std::vector<s16>> queue; | ||||||
|  |  | ||||||
|  |     static void Callback(void* impl_, u8* buffer, int buffer_size_in_bytes); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) { | ||||||
|  |     if (SDL_Init(SDL_INIT_AUDIO) < 0) { | ||||||
|  |         LOG_CRITICAL(Audio_Sink, "SDL_Init(SDL_INIT_AUDIO) failed"); | ||||||
|  |         impl->audio_device_id = 0; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     SDL_AudioSpec desired_audiospec; | ||||||
|  |     SDL_zero(desired_audiospec); | ||||||
|  |     desired_audiospec.format = AUDIO_S16; | ||||||
|  |     desired_audiospec.channels = 2; | ||||||
|  |     desired_audiospec.freq = native_sample_rate; | ||||||
|  |     desired_audiospec.samples = 1024; | ||||||
|  |     desired_audiospec.userdata = impl.get(); | ||||||
|  |     desired_audiospec.callback = &Impl::Callback; | ||||||
|  |  | ||||||
|  |     SDL_AudioSpec obtained_audiospec; | ||||||
|  |     SDL_zero(obtained_audiospec); | ||||||
|  |  | ||||||
|  |     impl->audio_device_id = SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0); | ||||||
|  |     if (impl->audio_device_id <= 0) { | ||||||
|  |         LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed"); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl->sample_rate = obtained_audiospec.freq; | ||||||
|  |  | ||||||
|  |     // SDL2 audio devices start out paused, unpause it: | ||||||
|  |     SDL_PauseAudioDevice(impl->audio_device_id, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SDL2Sink::~SDL2Sink() { | ||||||
|  |     if (impl->audio_device_id <= 0) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|  |     SDL_CloseAudioDevice(impl->audio_device_id); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | unsigned int SDL2Sink::GetNativeSampleRate() const { | ||||||
|  |     if (impl->audio_device_id <= 0) | ||||||
|  |         return native_sample_rate; | ||||||
|  |  | ||||||
|  |     return impl->sample_rate; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SDL2Sink::EnqueueSamples(const std::vector<s16>& samples) { | ||||||
|  |     if (impl->audio_device_id <= 0) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|  |     ASSERT_MSG(samples.size() % 2 == 0, "Samples must be in interleaved stereo PCM16 format (size must be a multiple of two)"); | ||||||
|  |  | ||||||
|  |     SDL_LockAudioDevice(impl->audio_device_id); | ||||||
|  |     impl->queue.emplace_back(samples); | ||||||
|  |     SDL_UnlockAudioDevice(impl->audio_device_id); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | size_t SDL2Sink::SamplesInQueue() const { | ||||||
|  |     if (impl->audio_device_id <= 0) | ||||||
|  |         return 0; | ||||||
|  |  | ||||||
|  |     SDL_LockAudioDevice(impl->audio_device_id); | ||||||
|  |  | ||||||
|  |     size_t total_size = std::accumulate(impl->queue.begin(), impl->queue.end(), static_cast<size_t>(0), | ||||||
|  |         [](size_t sum, const auto& buffer) { | ||||||
|  |             // Division by two because each stereo sample is made of two s16. | ||||||
|  |             return sum + buffer.size() / 2; | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |     SDL_UnlockAudioDevice(impl->audio_device_id); | ||||||
|  |  | ||||||
|  |     return total_size; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes) { | ||||||
|  |     Impl* impl = reinterpret_cast<Impl*>(impl_); | ||||||
|  |  | ||||||
|  |     size_t remaining_size = static_cast<size_t>(buffer_size_in_bytes) / sizeof(s16); // Keep track of size in 16-bit increments. | ||||||
|  |  | ||||||
|  |     while (remaining_size > 0 && !impl->queue.empty()) { | ||||||
|  |         if (impl->queue.front().size() <= remaining_size) { | ||||||
|  |             memcpy(buffer, impl->queue.front().data(), impl->queue.front().size() * sizeof(s16)); | ||||||
|  |             buffer += impl->queue.front().size() * sizeof(s16); | ||||||
|  |             remaining_size -= impl->queue.front().size(); | ||||||
|  |             impl->queue.pop_front(); | ||||||
|  |         } else { | ||||||
|  |             memcpy(buffer, impl->queue.front().data(), remaining_size * sizeof(s16)); | ||||||
|  |             buffer += remaining_size * sizeof(s16); | ||||||
|  |             impl->queue.front().erase(impl->queue.front().begin(), impl->queue.front().begin() + remaining_size); | ||||||
|  |             remaining_size = 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (remaining_size > 0) { | ||||||
|  |         memset(buffer, 0, remaining_size * sizeof(s16)); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace AudioCore | ||||||
							
								
								
									
										30
									
								
								src/audio_core/sdl2_sink.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/audio_core/sdl2_sink.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | // Copyright 2016 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <cstddef> | ||||||
|  | #include <memory> | ||||||
|  |  | ||||||
|  | #include "audio_core/sink.h" | ||||||
|  |  | ||||||
|  | namespace AudioCore { | ||||||
|  |  | ||||||
|  | class SDL2Sink final : public Sink { | ||||||
|  | public: | ||||||
|  |     SDL2Sink(); | ||||||
|  |     ~SDL2Sink() override; | ||||||
|  |  | ||||||
|  |     unsigned int GetNativeSampleRate() const override; | ||||||
|  |  | ||||||
|  |     void EnqueueSamples(const std::vector<s16>& samples) override; | ||||||
|  |  | ||||||
|  |     size_t SamplesInQueue() const override; | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     struct Impl; | ||||||
|  |     std::unique_ptr<Impl> impl; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace AudioCore | ||||||
| @@ -19,7 +19,7 @@ public: | |||||||
|     virtual ~Sink() = default; |     virtual ~Sink() = default; | ||||||
|  |  | ||||||
|     /// The native rate of this sink. The sink expects to be fed samples that respect this. (Units: samples/sec) |     /// The native rate of this sink. The sink expects to be fed samples that respect this. (Units: samples/sec) | ||||||
|     virtual unsigned GetNativeSampleRate() const = 0; |     virtual unsigned int GetNativeSampleRate() const = 0; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Feed stereo samples to sink. |      * Feed stereo samples to sink. | ||||||
|   | |||||||
| @@ -8,10 +8,17 @@ | |||||||
| #include "audio_core/null_sink.h" | #include "audio_core/null_sink.h" | ||||||
| #include "audio_core/sink_details.h" | #include "audio_core/sink_details.h" | ||||||
|  |  | ||||||
|  | #ifdef HAVE_SDL2 | ||||||
|  | #include "audio_core/sdl2_sink.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace AudioCore { | namespace AudioCore { | ||||||
|  |  | ||||||
| // g_sink_details is ordered in terms of desirability, with the best choice at the top. | // g_sink_details is ordered in terms of desirability, with the best choice at the top. | ||||||
| const std::vector<SinkDetails> g_sink_details = { | const std::vector<SinkDetails> g_sink_details = { | ||||||
|  | #ifdef HAVE_SDL2 | ||||||
|  |     { "sdl2", []() { return std::make_unique<SDL2Sink>(); } }, | ||||||
|  | #endif | ||||||
|     { "null", []() { return std::make_unique<NullSink>(); } }, |     { "null", []() { return std::make_unique<NullSink>(); } }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ bg_green = | |||||||
|  |  | ||||||
| [Audio] | [Audio] | ||||||
| # Which audio output engine to use. | # Which audio output engine to use. | ||||||
| # auto (default): Auto-select, null: No audio output | # auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available) | ||||||
| output_engine = | output_engine = | ||||||
|  |  | ||||||
| [Data Storage] | [Data Storage] | ||||||
|   | |||||||
| @@ -65,6 +65,7 @@ namespace Log { | |||||||
|         SUB(Render, OpenGL) \ |         SUB(Render, OpenGL) \ | ||||||
|         CLS(Audio) \ |         CLS(Audio) \ | ||||||
|         SUB(Audio, DSP) \ |         SUB(Audio, DSP) \ | ||||||
|  |         SUB(Audio, Sink) \ | ||||||
|         CLS(Loader) |         CLS(Loader) | ||||||
|  |  | ||||||
| // GetClassName is a macro defined by Windows.h, grrr... | // GetClassName is a macro defined by Windows.h, grrr... | ||||||
|   | |||||||
| @@ -78,8 +78,9 @@ enum class Class : ClassType { | |||||||
|     Render,                     ///< Emulator video output and hardware acceleration |     Render,                     ///< Emulator video output and hardware acceleration | ||||||
|     Render_Software,            ///< Software renderer backend |     Render_Software,            ///< Software renderer backend | ||||||
|     Render_OpenGL,              ///< OpenGL backend |     Render_OpenGL,              ///< OpenGL backend | ||||||
|     Audio,                      ///< Emulator audio output |     Audio,                      ///< Audio emulation | ||||||
|     Audio_DSP,                  ///< The HLE implementation of the DSP |     Audio_DSP,                  ///< The HLE implementation of the DSP | ||||||
|  |     Audio_Sink,                 ///< Emulator audio output backend | ||||||
|     Loader,                     ///< ROM loader |     Loader,                     ///< ROM loader | ||||||
|  |  | ||||||
|     Count ///< Total number of logging classes |     Count ///< Total number of logging classes | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user