diff --git a/CMakeLists.txt b/CMakeLists.txt index 18b8f7967d..9dfc06ac37 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 400988c5fb..e982d03bef 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 0000000000..fc62faaeee --- /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 0000000000..8f6f54ab53 --- /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 7c9a4e3acf..449af659d0 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 d6351e57e9..6170365886 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 01a4657d41..a9e895153e 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -41,6 +41,15 @@ "platform": "windows" } ] + }, + "android": { + "description": "Enable Android dependencies", + "dependencies": [ + { + "name": "oboe", + "platform": "android" + } + ] } }, "overrides": [