From 7239547eada6ad4a24e65957dfab180a51818947 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 17 Dec 2023 00:11:20 -0500 Subject: android: add oboe audio sink --- CMakeLists.txt | 3 + src/audio_core/CMakeLists.txt | 11 +++ src/audio_core/sink/oboe_sink.cpp | 184 +++++++++++++++++++++++++++++++++++ src/audio_core/sink/oboe_sink.h | 75 ++++++++++++++ src/audio_core/sink/sink_details.cpp | 13 +++ src/common/settings_enums.h | 7 +- vcpkg.json | 9 ++ 7 files changed, 298 insertions(+), 4 deletions(-) create mode 100644 src/audio_core/sink/oboe_sink.cpp create mode 100644 src/audio_core/sink/oboe_sink.h 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/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..fc62faaee --- /dev/null +++ b/src/audio_core/sink/oboe_sink.cpp @@ -0,0 +1,184 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include + +#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 device_channels_, u32 system_channels_) + : SinkStream(system_, type_) { + name = name_; + system_channels = system_channels_; + device_channels = device_channels_; + + this->OpenStream(); + } + + ~OboeSinkStream() override { + LOG_DEBUG(Audio_Sink, "Destructing Oboe stream {}", name); + } + + 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"); + } + } + +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(num_buffer_frames); + + if (type == StreamType::In) { + std::span input_buffer{reinterpret_cast(audio_data), + num_frames * frame_size}; + this->ProcessAudioIn(input_buffer, num_frames); + } else { + std::span output_buffer{reinterpret_cast(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: + 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 channel_mask = [&]() { + switch (device_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 = builder.setDirection(direction) + ->setSampleRate(TargetSampleRate) + ->setChannelCount(device_channels) + ->setChannelMask(channel_mask) + ->setFormat(oboe::AudioFormat::I16) + ->setFormatConversionAllowed(true) + ->setDataCallback(this) + ->setErrorCallback(this) + ->openStream(m_stream); + + ASSERT(result == oboe::Result::OK); + return result == oboe::Result::OK; + } + + std::shared_ptr m_stream{}; +}; + +OboeSink::OboeSink() { + // TODO: how do we get the number of channels, or device list? + // This seems to be missing from NDK. + device_channels = 2; +} + +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(system, type, name, device_channels, 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 +#include + +#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 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 #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 { + return std::make_unique(); + }, + [](bool capture) { return std::vector{"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> EnumMetadata::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": [ -- cgit v1.2.3 From e01c5351781f7c0a8f5e6469a33563f26f8f0779 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 17 Dec 2023 02:01:25 -0500 Subject: oboe_sink: implement channel count querying --- src/audio_core/sink/oboe_sink.cpp | 47 +++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/audio_core/sink/oboe_sink.cpp b/src/audio_core/sink/oboe_sink.cpp index fc62faaee..c502205e1 100644 --- a/src/audio_core/sink/oboe_sink.cpp +++ b/src/audio_core/sink/oboe_sink.cpp @@ -20,11 +20,10 @@ class OboeSinkStream final : public SinkStream, public oboe::AudioStreamErrorCallback { public: explicit OboeSinkStream(Core::System& system_, StreamType type_, const std::string& name_, - u32 device_channels_, u32 system_channels_) + u32 system_channels_) : SinkStream(system_, type_) { name = name_; system_channels = system_channels_; - device_channels = device_channels_; this->OpenStream(); } @@ -62,6 +61,21 @@ public: } } +public: + static s32 QueryChannelCount(oboe::Direction direction) { + std::shared_ptr temp_stream; + oboe::AudioStreamBuilder builder; + + const auto result = builder.setDirection(direction) + ->setSampleRate(TargetSampleRate) + ->setFormat(oboe::AudioFormat::I16) + ->setFormatConversionAllowed(true) + ->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 { @@ -105,8 +119,9 @@ private: } }(); - const auto channel_mask = [&]() { - switch (device_channels) { + const auto expected_channels = QueryChannelCount(direction); + const auto expected_mask = [&]() { + switch (expected_channels) { case 1: return oboe::ChannelMask::Mono; case 2: @@ -122,25 +137,33 @@ private: oboe::AudioStreamBuilder builder; const auto result = builder.setDirection(direction) ->setSampleRate(TargetSampleRate) - ->setChannelCount(device_channels) - ->setChannelMask(channel_mask) + ->setChannelCount(expected_channels) + ->setChannelMask(expected_mask) ->setFormat(oboe::AudioFormat::I16) ->setFormatConversionAllowed(true) ->setDataCallback(this) ->setErrorCallback(this) ->openStream(m_stream); - ASSERT(result == oboe::Result::OK); - return result == oboe::Result::OK; + return result == oboe::Result::OK && this->SetStreamProperties(); + } + + bool SetStreamProperties() { + ASSERT(m_stream); + + device_channels = m_stream->getChannelCount(); + LOG_INFO(Audio_Sink, "Opened Oboe stream with {} channels", device_channels); + + return true; } std::shared_ptr m_stream{}; }; OboeSink::OboeSink() { - // TODO: how do we get the number of channels, or device list? - // This seems to be missing from NDK. - device_channels = 2; + // 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; @@ -148,7 +171,7 @@ 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(system, type, name, device_channels, system_channels)); + std::make_unique(system, type, name, system_channels)); return stream.get(); } -- cgit v1.2.3 From 6ca530a72164878718a2bded90bf139bf4636873 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 17 Dec 2023 11:44:49 -0500 Subject: android: add oboe to audio configuration --- src/android/app/src/main/res/values/arrays.xml | 2 ++ src/android/app/src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+) 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/auto + @string/oboe @string/cubeb @string/string_null 0 + 4 1 3 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 @@ Dark + oboe cubeb -- cgit v1.2.3 From 797e8fdbc3b6a9220544596099ceddea0739e31c Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 17 Dec 2023 21:05:00 -0500 Subject: oboe_sink: set low latency performance mode --- src/audio_core/sink/oboe_sink.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/audio_core/sink/oboe_sink.cpp b/src/audio_core/sink/oboe_sink.cpp index c502205e1..8143b0db6 100644 --- a/src/audio_core/sink/oboe_sink.cpp +++ b/src/audio_core/sink/oboe_sink.cpp @@ -67,6 +67,7 @@ public: oboe::AudioStreamBuilder builder; const auto result = builder.setDirection(direction) + ->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->setSampleRate(TargetSampleRate) ->setFormat(oboe::AudioFormat::I16) ->setFormatConversionAllowed(true) @@ -136,6 +137,7 @@ private: oboe::AudioStreamBuilder builder; const auto result = builder.setDirection(direction) + ->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->setSampleRate(TargetSampleRate) ->setChannelCount(expected_channels) ->setChannelMask(expected_mask) -- cgit v1.2.3 From a7731abb72e80c26d5af771d17233652435a0c7d Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 18 Dec 2023 09:22:20 -0500 Subject: oboe_sink: specify additional required parameters --- src/audio_core/sink/oboe_sink.cpp | 40 ++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/audio_core/sink/oboe_sink.cpp b/src/audio_core/sink/oboe_sink.cpp index 8143b0db6..e61841172 100644 --- a/src/audio_core/sink/oboe_sink.cpp +++ b/src/audio_core/sink/oboe_sink.cpp @@ -29,7 +29,7 @@ public: } ~OboeSinkStream() override { - LOG_DEBUG(Audio_Sink, "Destructing Oboe stream {}", name); + LOG_INFO(Audio_Sink, "Destroyed Oboe stream"); } void Finalize() override { @@ -66,12 +66,7 @@ public: std::shared_ptr temp_stream; oboe::AudioStreamBuilder builder; - const auto result = builder.setDirection(direction) - ->setPerformanceMode(oboe::PerformanceMode::LowLatency) - ->setSampleRate(TargetSampleRate) - ->setFormat(oboe::AudioFormat::I16) - ->setFormatConversionAllowed(true) - ->openStream(temp_stream); + const auto result = ConfigureBuilder(builder, direction)->openStream(temp_stream); ASSERT(result == oboe::Result::OK); return temp_stream->getChannelCount() >= 6 ? 6 : 2; @@ -106,6 +101,20 @@ protected: } 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) { @@ -136,13 +145,10 @@ private: }(); oboe::AudioStreamBuilder builder; - const auto result = builder.setDirection(direction) - ->setPerformanceMode(oboe::PerformanceMode::LowLatency) - ->setSampleRate(TargetSampleRate) + const auto result = ConfigureBuilder(builder, direction) ->setChannelCount(expected_channels) ->setChannelMask(expected_mask) - ->setFormat(oboe::AudioFormat::I16) - ->setFormatConversionAllowed(true) + ->setChannelConversionAllowed(true) ->setDataCallback(this) ->setErrorCallback(this) ->openStream(m_stream); @@ -153,8 +159,16 @@ private: bool SetStreamProperties() { ASSERT(m_stream); + m_stream->setBufferSizeInFrames(TargetSampleCount * 2); device_channels = m_stream->getChannelCount(); - LOG_INFO(Audio_Sink, "Opened Oboe stream with {} channels", device_channels); + + 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; } -- cgit v1.2.3