Browse Source
[android] Replace Oboe audio with SDL3 audio (#4338)
[android] Replace Oboe audio with SDL3 audio (#4338)
Supersedes #4335. Replaces the Oboe audio backend with SDL3, since we already support it. A few caveats: - SDL defines a JVM OnLoad method already, so we have to instead hijack NativeLibrary for id_cache initialization - This also means some internal fields in Double/Integer/Boolean (namely `value`) can't be accessed, so I switched those to methods for now. - SDL Java sources are pulled in from the CI package. - Since we can't use SDLActivity, `SDL.setupJNI()` handles everything Signed-off-by: crueter <crueter@eden-emu.dev> - [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). - [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md) - [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. ------------------- Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4338 Reviewed-by: Lizzie and Samuel <lizzie@eden-emu.dev> Reviewed-by: MaranBr <maranbr@eden-emu.dev>pull/4339/head
No known key found for this signature in database
GPG Key ID: 425ACD2D4830EBC6
21 changed files with 274 additions and 597 deletions
-
3.gitignore
-
4CMakeLists.txt
-
6cpmfile.json
-
80externals/CMakeLists.txt
-
7src/android/app/build.gradle.kts
-
3src/android/app/proguard-rules.pro
-
4src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
-
2src/android/app/src/main/jni/CMakeLists.txt
-
9src/android/app/src/main/jni/native.cpp
-
1src/android/app/src/main/res/values-fa/strings.xml
-
4src/android/app/src/main/res/values/arrays.xml
-
2src/android/app/src/main/res/values/strings.xml
-
19src/audio_core/CMakeLists.txt
-
230src/audio_core/sink/oboe_sink.cpp
-
75src/audio_core/sink/oboe_sink.h
-
19src/audio_core/sink/sink_details.cpp
-
8src/common/android/android_common.cpp
-
380src/common/android/id_cache.cpp
-
7src/common/android/id_cache.h
-
6src/common/settings_enums.h
-
2src/input_common/drivers/android.cpp
@ -1,230 +0,0 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
|||
// 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.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); |
|||
if (result == oboe::Result::OK) { |
|||
return temp_stream->getChannelCount() >= 6 ? 6 : 2; |
|||
} |
|||
|
|||
LOG_ERROR(Audio_Sink, "Failed to open {} stream. Using default channel count 2", |
|||
direction == oboe::Direction::Output ? "output" : "input"); |
|||
return 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
|
|||
@ -1,75 +0,0 @@ |
|||
// 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 |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue