diff options
author | Fernando S <fsahmkow27@gmail.com> | 2023-12-19 16:15:07 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-19 16:15:07 +0100 |
commit | d61df0f4002274b23c0c7139449906ca392a6a3d (patch) | |
tree | 18195cd952b7d988570b3586ec1d02feed4a079f | |
parent | Merge pull request #12392 from liamwhite/mode (diff) | |
parent | oboe_sink: specify additional required parameters (diff) | |
download | yuzu-d61df0f4002274b23c0c7139449906ca392a6a3d.tar yuzu-d61df0f4002274b23c0c7139449906ca392a6a3d.tar.gz yuzu-d61df0f4002274b23c0c7139449906ca392a6a3d.tar.bz2 yuzu-d61df0f4002274b23c0c7139449906ca392a6a3d.tar.lz yuzu-d61df0f4002274b23c0c7139449906ca392a6a3d.tar.xz yuzu-d61df0f4002274b23c0c7139449906ca392a6a3d.tar.zst yuzu-d61df0f4002274b23c0c7139449906ca392a6a3d.zip |
-rw-r--r-- | CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/android/app/src/main/res/values/arrays.xml | 2 | ||||
-rw-r--r-- | src/android/app/src/main/res/values/strings.xml | 1 | ||||
-rw-r--r-- | src/audio_core/CMakeLists.txt | 11 | ||||
-rw-r--r-- | src/audio_core/sink/oboe_sink.cpp | 223 | ||||
-rw-r--r-- | src/audio_core/sink/oboe_sink.h | 75 | ||||
-rw-r--r-- | src/audio_core/sink/sink_details.cpp | 13 | ||||
-rw-r--r-- | src/common/settings_enums.h | 7 | ||||
-rw-r--r-- | vcpkg.json | 9 |
9 files changed, 340 insertions, 4 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 18b8f7967..9dfc06ac3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,9 @@ if (YUZU_USE_BUNDLED_VCPKG) if (ENABLE_WEB_SERVICE) list(APPEND VCPKG_MANIFEST_FEATURES "web-service") endif() + if (ANDROID) + list(APPEND VCPKG_MANIFEST_FEATURES "android") + endif() include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake) elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "") diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index ab435dce9..e3915ef4f 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -256,11 +256,13 @@ <string-array name="outputEngineEntries"> <item>@string/auto</item> + <item>@string/oboe</item> <item>@string/cubeb</item> <item>@string/string_null</item> </string-array> <integer-array name="outputEngineValues"> <item>0</item> + <item>4</item> <item>1</item> <item>3</item> </integer-array> diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index c86c43df2..0b80b04a4 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -503,6 +503,7 @@ <string name="theme_mode_dark">Dark</string> <!-- Audio output engines --> + <string name="oboe">oboe</string> <string name="cubeb">cubeb</string> <!-- Black backgrounds theme --> diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 400988c5f..e982d03be 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -253,6 +253,17 @@ if (ENABLE_SDL2) target_compile_definitions(audio_core PRIVATE HAVE_SDL2) endif() +if (ANDROID) + target_sources(audio_core PRIVATE + sink/oboe_sink.cpp + sink/oboe_sink.h + ) + + # FIXME: this port seems broken, it cannot be imported with find_package(oboe REQUIRED) + target_link_libraries(audio_core PRIVATE "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib/liboboe.a") + target_compile_definitions(audio_core PRIVATE HAVE_OBOE) +endif() + if (YUZU_USE_PRECOMPILED_HEADERS) target_precompile_headers(audio_core PRIVATE precompiled_headers.h) endif() diff --git a/src/audio_core/sink/oboe_sink.cpp b/src/audio_core/sink/oboe_sink.cpp new file mode 100644 index 000000000..e61841172 --- /dev/null +++ b/src/audio_core/sink/oboe_sink.cpp @@ -0,0 +1,223 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <span> +#include <vector> + +#include <oboe/Oboe.h> + +#include "audio_core/common/common.h" +#include "audio_core/sink/oboe_sink.h" +#include "audio_core/sink/sink_stream.h" +#include "common/logging/log.h" +#include "common/scope_exit.h" +#include "core/core.h" + +namespace AudioCore::Sink { + +class OboeSinkStream final : public SinkStream, + public oboe::AudioStreamDataCallback, + public oboe::AudioStreamErrorCallback { +public: + explicit OboeSinkStream(Core::System& system_, StreamType type_, const std::string& name_, + u32 system_channels_) + : SinkStream(system_, type_) { + name = name_; + system_channels = system_channels_; + + this->OpenStream(); + } + + ~OboeSinkStream() override { + LOG_INFO(Audio_Sink, "Destroyed Oboe stream"); + } + + void Finalize() override { + this->Stop(); + m_stream.reset(); + } + + void Start(bool resume = false) override { + if (!m_stream || !paused) { + return; + } + + paused = false; + + if (m_stream->start() != oboe::Result::OK) { + LOG_CRITICAL(Audio_Sink, "Error starting Oboe stream"); + } + } + + void Stop() override { + if (!m_stream || paused) { + return; + } + + this->SignalPause(); + + if (m_stream->stop() != oboe::Result::OK) { + LOG_CRITICAL(Audio_Sink, "Error stopping Oboe stream"); + } + } + +public: + static s32 QueryChannelCount(oboe::Direction direction) { + std::shared_ptr<oboe::AudioStream> temp_stream; + oboe::AudioStreamBuilder builder; + + const auto result = ConfigureBuilder(builder, direction)->openStream(temp_stream); + ASSERT(result == oboe::Result::OK); + + return temp_stream->getChannelCount() >= 6 ? 6 : 2; + } + +protected: + oboe::DataCallbackResult onAudioReady(oboe::AudioStream*, void* audio_data, + s32 num_buffer_frames) override { + const size_t num_channels = this->GetDeviceChannels(); + const size_t frame_size = num_channels; + const size_t num_frames = static_cast<size_t>(num_buffer_frames); + + if (type == StreamType::In) { + std::span<const s16> input_buffer{reinterpret_cast<const s16*>(audio_data), + num_frames * frame_size}; + this->ProcessAudioIn(input_buffer, num_frames); + } else { + std::span<s16> output_buffer{reinterpret_cast<s16*>(audio_data), + num_frames * frame_size}; + this->ProcessAudioOutAndRender(output_buffer, num_frames); + } + + return oboe::DataCallbackResult::Continue; + } + + void onErrorAfterClose(oboe::AudioStream*, oboe::Result) override { + LOG_INFO(Audio_Sink, "Audio stream closed, reinitializing"); + + if (this->OpenStream()) { + m_stream->start(); + } + } + +private: + static oboe::AudioStreamBuilder* ConfigureBuilder(oboe::AudioStreamBuilder& builder, + oboe::Direction direction) { + // TODO: investigate callback delay issues when using AAudio + return builder.setPerformanceMode(oboe::PerformanceMode::LowLatency) + ->setAudioApi(oboe::AudioApi::OpenSLES) + ->setDirection(direction) + ->setSampleRate(TargetSampleRate) + ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High) + ->setFormat(oboe::AudioFormat::I16) + ->setFormatConversionAllowed(true) + ->setUsage(oboe::Usage::Game) + ->setBufferCapacityInFrames(TargetSampleCount * 2); + } + + bool OpenStream() { + const auto direction = [&]() { + switch (type) { + case StreamType::In: + return oboe::Direction::Input; + case StreamType::Out: + case StreamType::Render: + return oboe::Direction::Output; + default: + ASSERT(false); + return oboe::Direction::Output; + } + }(); + + const auto expected_channels = QueryChannelCount(direction); + const auto expected_mask = [&]() { + switch (expected_channels) { + case 1: + return oboe::ChannelMask::Mono; + case 2: + return oboe::ChannelMask::Stereo; + case 6: + return oboe::ChannelMask::CM5Point1; + default: + ASSERT(false); + return oboe::ChannelMask::Unspecified; + } + }(); + + oboe::AudioStreamBuilder builder; + const auto result = ConfigureBuilder(builder, direction) + ->setChannelCount(expected_channels) + ->setChannelMask(expected_mask) + ->setChannelConversionAllowed(true) + ->setDataCallback(this) + ->setErrorCallback(this) + ->openStream(m_stream); + ASSERT(result == oboe::Result::OK); + return result == oboe::Result::OK && this->SetStreamProperties(); + } + + bool SetStreamProperties() { + ASSERT(m_stream); + + m_stream->setBufferSizeInFrames(TargetSampleCount * 2); + device_channels = m_stream->getChannelCount(); + + const auto sample_rate = m_stream->getSampleRate(); + const auto buffer_capacity = m_stream->getBufferCapacityInFrames(); + const auto stream_backend = + m_stream->getAudioApi() == oboe::AudioApi::AAudio ? "AAudio" : "OpenSLES"; + + LOG_INFO(Audio_Sink, "Opened Oboe {} stream with {} channels sample rate {} capacity {}", + stream_backend, device_channels, sample_rate, buffer_capacity); + + return true; + } + + std::shared_ptr<oboe::AudioStream> m_stream{}; +}; + +OboeSink::OboeSink() { + // TODO: This is not generally knowable + // The channel count is distinct based on direction and can change + device_channels = OboeSinkStream::QueryChannelCount(oboe::Direction::Output); +} + +OboeSink::~OboeSink() = default; + +SinkStream* OboeSink::AcquireSinkStream(Core::System& system, u32 system_channels, + const std::string& name, StreamType type) { + SinkStreamPtr& stream = sink_streams.emplace_back( + std::make_unique<OboeSinkStream>(system, type, name, system_channels)); + + return stream.get(); +} + +void OboeSink::CloseStream(SinkStream* to_remove) { + sink_streams.remove_if([&](auto& stream) { return stream.get() == to_remove; }); +} + +void OboeSink::CloseStreams() { + sink_streams.clear(); +} + +f32 OboeSink::GetDeviceVolume() const { + if (sink_streams.empty()) { + return 1.0f; + } + + return sink_streams.front()->GetDeviceVolume(); +} + +void OboeSink::SetDeviceVolume(f32 volume) { + for (auto& stream : sink_streams) { + stream->SetDeviceVolume(volume); + } +} + +void OboeSink::SetSystemVolume(f32 volume) { + for (auto& stream : sink_streams) { + stream->SetSystemVolume(volume); + } +} + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/oboe_sink.h b/src/audio_core/sink/oboe_sink.h new file mode 100644 index 000000000..8f6f54ab5 --- /dev/null +++ b/src/audio_core/sink/oboe_sink.h @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <list> +#include <string> + +#include "audio_core/sink/sink.h" + +namespace Core { +class System; +} + +namespace AudioCore::Sink { +class SinkStream; + +class OboeSink final : public Sink { +public: + explicit OboeSink(); + ~OboeSink() override; + + /** + * Create a new sink stream. + * + * @param system - Core system. + * @param system_channels - Number of channels the audio system expects. + * May differ from the device's channel count. + * @param name - Name of this stream. + * @param type - Type of this stream, render/in/out. + * + * @return A pointer to the created SinkStream + */ + SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, + const std::string& name, StreamType type) override; + + /** + * Close a given stream. + * + * @param stream - The stream to close. + */ + void CloseStream(SinkStream* stream) override; + + /** + * Close all streams. + */ + void CloseStreams() override; + + /** + * Get the device volume. Set from calls to the IAudioDevice service. + * + * @return Volume of the device. + */ + f32 GetDeviceVolume() const override; + + /** + * Set the device volume. Set from calls to the IAudioDevice service. + * + * @param volume - New volume of the device. + */ + void SetDeviceVolume(f32 volume) override; + + /** + * Set the system volume. Comes from the audio system using this stream. + * + * @param volume - New volume of the system. + */ + void SetSystemVolume(f32 volume) override; + +private: + /// List of streams managed by this sink + std::list<SinkStreamPtr> sink_streams{}; +}; + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp index 7c9a4e3ac..449af659d 100644 --- a/src/audio_core/sink/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp @@ -7,6 +7,9 @@ #include <vector> #include "audio_core/sink/sink_details.h" +#ifdef HAVE_OBOE +#include "audio_core/sink/oboe_sink.h" +#endif #ifdef HAVE_CUBEB #include "audio_core/sink/cubeb_sink.h" #endif @@ -36,6 +39,16 @@ struct SinkDetails { // sink_details is ordered in terms of desirability, with the best choice at the top. constexpr SinkDetails sink_details[] = { +#ifdef HAVE_OBOE + SinkDetails{ + Settings::AudioEngine::Oboe, + [](std::string_view device_id) -> std::unique_ptr<Sink> { + return std::make_unique<OboeSink>(); + }, + [](bool capture) { return std::vector<std::string>{"Default"}; }, + []() { return true; }, + }, +#endif #ifdef HAVE_CUBEB SinkDetails{ Settings::AudioEngine::Cubeb, diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index d6351e57e..617036588 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -82,16 +82,15 @@ enum class AudioEngine : u32 { Cubeb, Sdl2, Null, + Oboe, }; template <> inline std::vector<std::pair<std::string, AudioEngine>> EnumMetadata<AudioEngine>::Canonicalizations() { return { - {"auto", AudioEngine::Auto}, - {"cubeb", AudioEngine::Cubeb}, - {"sdl2", AudioEngine::Sdl2}, - {"null", AudioEngine::Null}, + {"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2}, + {"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe}, }; } diff --git a/vcpkg.json b/vcpkg.json index 01a4657d4..a9e895153 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -41,6 +41,15 @@ "platform": "windows" } ] + }, + "android": { + "description": "Enable Android dependencies", + "dependencies": [ + { + "name": "oboe", + "platform": "android" + } + ] } }, "overrides": [ |