Merge pull request #12387 from liamwhite/oboe
android: add oboe audio sink
This commit is contained in:
		| @@ -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() | ||||
|   | ||||
							
								
								
									
										223
									
								
								src/audio_core/sink/oboe_sink.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								src/audio_core/sink/oboe_sink.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										75
									
								
								src/audio_core/sink/oboe_sink.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/audio_core/sink/oboe_sink.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -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, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Fernando S
					Fernando S