Android: Add OpenSL ES audio backend
This commit is contained in:
parent
82f00237af
commit
962f3407b4
|
@ -7,3 +7,13 @@ set(SRCS
|
||||||
|
|
||||||
add_library(duckstation-native SHARED ${SRCS})
|
add_library(duckstation-native SHARED ${SRCS})
|
||||||
target_link_libraries(duckstation-native PRIVATE android frontend-common core common glad imgui)
|
target_link_libraries(duckstation-native PRIVATE android frontend-common core common glad imgui)
|
||||||
|
|
||||||
|
find_package(OpenSLES)
|
||||||
|
if(OPENSLES_FOUND)
|
||||||
|
message("Enabling OpenSL ES audio stream")
|
||||||
|
target_sources(duckstation-native PRIVATE
|
||||||
|
opensles_audio_stream.cpp
|
||||||
|
opensles_audio_stream.h)
|
||||||
|
target_link_libraries(duckstation-native PRIVATE OpenSLES::OpenSLES)
|
||||||
|
target_compile_definitions(duckstation-native PRIVATE "-DUSE_OPENSLES=1")
|
||||||
|
endif()
|
||||||
|
|
|
@ -20,6 +20,10 @@
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
Log_SetChannel(AndroidHostInterface);
|
Log_SetChannel(AndroidHostInterface);
|
||||||
|
|
||||||
|
#ifdef USE_OPENSLES
|
||||||
|
#include "opensles_audio_stream.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
static JavaVM* s_jvm;
|
static JavaVM* s_jvm;
|
||||||
static jclass s_AndroidHostInterface_class;
|
static jclass s_AndroidHostInterface_class;
|
||||||
static jmethodID s_AndroidHostInterface_constructor;
|
static jmethodID s_AndroidHostInterface_constructor;
|
||||||
|
@ -394,6 +398,16 @@ void AndroidHostInterface::ReleaseHostDisplay()
|
||||||
m_display.reset();
|
m_display.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AudioStream> AndroidHostInterface::CreateAudioStream(AudioBackend backend)
|
||||||
|
{
|
||||||
|
#ifdef USE_OPENSLES
|
||||||
|
if (backend == AudioBackend::OpenSLES)
|
||||||
|
return OpenSLESAudioStream::Create();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return CommonHostInterface::CreateAudioStream(backend);
|
||||||
|
}
|
||||||
|
|
||||||
void AndroidHostInterface::OnSystemDestroyed()
|
void AndroidHostInterface::OnSystemDestroyed()
|
||||||
{
|
{
|
||||||
CommonHostInterface::OnSystemDestroyed();
|
CommonHostInterface::OnSystemDestroyed();
|
||||||
|
|
|
@ -59,6 +59,7 @@ protected:
|
||||||
|
|
||||||
bool AcquireHostDisplay() override;
|
bool AcquireHostDisplay() override;
|
||||||
void ReleaseHostDisplay() override;
|
void ReleaseHostDisplay() override;
|
||||||
|
std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) override;
|
||||||
|
|
||||||
void OnSystemDestroyed() override;
|
void OnSystemDestroyed() override;
|
||||||
void OnRunningGameChanged() override;
|
void OnRunningGameChanged() override;
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
#include "opensles_audio_stream.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include <cmath>
|
||||||
|
Log_SetChannel(OpenSLESAudioStream);
|
||||||
|
|
||||||
|
// Based off Dolphin's OpenSLESStream class.
|
||||||
|
|
||||||
|
OpenSLESAudioStream::OpenSLESAudioStream() = default;
|
||||||
|
|
||||||
|
OpenSLESAudioStream::~OpenSLESAudioStream()
|
||||||
|
{
|
||||||
|
if (IsOpen())
|
||||||
|
OpenSLESAudioStream::CloseDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AudioStream> OpenSLESAudioStream::Create()
|
||||||
|
{
|
||||||
|
return std::make_unique<OpenSLESAudioStream>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenSLESAudioStream::OpenDevice()
|
||||||
|
{
|
||||||
|
DebugAssert(!IsOpen());
|
||||||
|
|
||||||
|
SLresult res = slCreateEngine(&m_engine, 0, nullptr, 0, nullptr, nullptr);
|
||||||
|
if (res != SL_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("slCreateEngine failed: %d", res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = (*m_engine)->Realize(m_engine, SL_BOOLEAN_FALSE);
|
||||||
|
if (res == SL_RESULT_SUCCESS)
|
||||||
|
res = (*m_engine)->GetInterface(m_engine, SL_IID_ENGINE, &m_engine_engine);
|
||||||
|
if (res == SL_RESULT_SUCCESS)
|
||||||
|
res = (*m_engine_engine)->CreateOutputMix(m_engine_engine, &m_output_mix, 0, 0, 0);
|
||||||
|
if (res == SL_RESULT_SUCCESS)
|
||||||
|
res = (*m_output_mix)->Realize(m_output_mix, SL_BOOLEAN_FALSE);
|
||||||
|
if (res != SL_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to create engine/output mix");
|
||||||
|
CloseDevice();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SLDataLocator_AndroidSimpleBufferQueue dloc_bq{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, NUM_BUFFERS};
|
||||||
|
SLDataFormat_PCM format = {SL_DATAFORMAT_PCM,
|
||||||
|
m_channels,
|
||||||
|
m_output_sample_rate * 1000u,
|
||||||
|
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||||
|
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||||
|
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
||||||
|
SL_BYTEORDER_LITTLEENDIAN};
|
||||||
|
SLDataSource dsrc{&dloc_bq, &format};
|
||||||
|
SLDataLocator_OutputMix dloc_outputmix{SL_DATALOCATOR_OUTPUTMIX, m_output_mix};
|
||||||
|
SLDataSink dsink{&dloc_outputmix, nullptr};
|
||||||
|
|
||||||
|
const std::array<SLInterfaceID, 2> ap_interfaces = {{SL_IID_BUFFERQUEUE, SL_IID_VOLUME}};
|
||||||
|
const std::array<SLboolean, 2> ap_interfaces_req = {{true, true}};
|
||||||
|
res = (*m_engine_engine)
|
||||||
|
->CreateAudioPlayer(m_engine_engine, &m_player, &dsrc, &dsink, static_cast<u32>(ap_interfaces.size()),
|
||||||
|
ap_interfaces.data(), ap_interfaces_req.data());
|
||||||
|
if (res != SL_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to create audio player: %d", res);
|
||||||
|
CloseDevice();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = (*m_player)->Realize(m_player, SL_BOOLEAN_FALSE);
|
||||||
|
if (res == SL_RESULT_SUCCESS)
|
||||||
|
res = (*m_player)->GetInterface(m_player, SL_IID_PLAY, &m_play_interface);
|
||||||
|
if (res == SL_RESULT_SUCCESS)
|
||||||
|
res = (*m_player)->GetInterface(m_player, SL_IID_BUFFERQUEUE, &m_buffer_queue_interface);
|
||||||
|
if (res == SL_RESULT_SUCCESS)
|
||||||
|
res = (*m_player)->GetInterface(m_player, SL_IID_VOLUME, &m_volume_interface);
|
||||||
|
if (res != SL_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to get player interfaces: %d", res);
|
||||||
|
CloseDevice();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = (*m_buffer_queue_interface)->RegisterCallback(m_buffer_queue_interface, BufferCallback, this);
|
||||||
|
if (res != SL_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to register callback: %d", res);
|
||||||
|
CloseDevice();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 i = 0; i < NUM_BUFFERS; i++)
|
||||||
|
m_buffers[i] = std::make_unique<SampleType[]>(m_buffer_size * m_channels);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenSLESAudioStream::PauseDevice(bool paused)
|
||||||
|
{
|
||||||
|
if (m_paused == paused)
|
||||||
|
return;
|
||||||
|
|
||||||
|
(*m_play_interface)->SetPlayState(m_play_interface, paused ? SL_PLAYSTATE_PAUSED : SL_PLAYSTATE_PLAYING);
|
||||||
|
|
||||||
|
if (!paused && !m_buffer_enqueued)
|
||||||
|
{
|
||||||
|
m_buffer_enqueued = true;
|
||||||
|
EnqueueBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_paused = paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenSLESAudioStream::CloseDevice()
|
||||||
|
{
|
||||||
|
m_buffers = {};
|
||||||
|
m_current_buffer = 0;
|
||||||
|
m_paused = true;
|
||||||
|
m_buffer_enqueued = false;
|
||||||
|
|
||||||
|
if (m_player)
|
||||||
|
{
|
||||||
|
(*m_player)->Destroy(m_player);
|
||||||
|
m_volume_interface = {};
|
||||||
|
m_buffer_queue_interface = {};
|
||||||
|
m_play_interface = {};
|
||||||
|
m_player = {};
|
||||||
|
}
|
||||||
|
if (m_output_mix)
|
||||||
|
{
|
||||||
|
(*m_output_mix)->Destroy(m_output_mix);
|
||||||
|
m_output_mix = {};
|
||||||
|
}
|
||||||
|
(*m_engine)->Destroy(m_engine);
|
||||||
|
m_engine_engine = {};
|
||||||
|
m_engine = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenSLESAudioStream::SetOutputVolume(u32 volume)
|
||||||
|
{
|
||||||
|
const SLmillibel attenuation = (volume == 0) ?
|
||||||
|
SL_MILLIBEL_MIN :
|
||||||
|
static_cast<SLmillibel>(2000.0f * std::log10(static_cast<float>(volume) / 100.0f));
|
||||||
|
(*m_volume_interface)->SetVolumeLevel(m_volume_interface, attenuation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenSLESAudioStream::EnqueueBuffer()
|
||||||
|
{
|
||||||
|
SampleType* samples = m_buffers[m_current_buffer].get();
|
||||||
|
ReadFrames(samples, m_buffer_size, false);
|
||||||
|
|
||||||
|
SLresult res = (*m_buffer_queue_interface)
|
||||||
|
->Enqueue(m_buffer_queue_interface, samples, m_buffer_size * m_channels * sizeof(SampleType));
|
||||||
|
if (res != SL_RESULT_SUCCESS)
|
||||||
|
Log_ErrorPrintf("Enqueue buffer failed: %d", res);
|
||||||
|
|
||||||
|
m_current_buffer = (m_current_buffer + 1) % NUM_BUFFERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenSLESAudioStream::BufferCallback(SLAndroidSimpleBufferQueueItf buffer_queue, void* context)
|
||||||
|
{
|
||||||
|
OpenSLESAudioStream* const this_ptr = static_cast<OpenSLESAudioStream*>(context);
|
||||||
|
this_ptr->EnqueueBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenSLESAudioStream::FramesAvailable() {}
|
|
@ -0,0 +1,48 @@
|
||||||
|
#pragma once
|
||||||
|
#include "common/audio_stream.h"
|
||||||
|
#include <SLES/OpenSLES.h>
|
||||||
|
#include <SLES/OpenSLES_Android.h>
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class OpenSLESAudioStream final : public AudioStream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OpenSLESAudioStream();
|
||||||
|
~OpenSLESAudioStream();
|
||||||
|
|
||||||
|
static std::unique_ptr<AudioStream> Create();
|
||||||
|
|
||||||
|
void SetOutputVolume(u32 volume) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
enum : u32
|
||||||
|
{
|
||||||
|
NUM_BUFFERS = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
ALWAYS_INLINE bool IsOpen() const { return (m_engine != nullptr); }
|
||||||
|
|
||||||
|
bool OpenDevice() override;
|
||||||
|
void PauseDevice(bool paused) override;
|
||||||
|
void CloseDevice() override;
|
||||||
|
void FramesAvailable() override;
|
||||||
|
|
||||||
|
void EnqueueBuffer();
|
||||||
|
|
||||||
|
static void BufferCallback(SLAndroidSimpleBufferQueueItf buffer_queue, void* context);
|
||||||
|
|
||||||
|
SLObjectItf m_engine{};
|
||||||
|
SLEngineItf m_engine_engine{};
|
||||||
|
SLObjectItf m_output_mix{};
|
||||||
|
|
||||||
|
SLObjectItf m_player{};
|
||||||
|
SLPlayItf m_play_interface{};
|
||||||
|
SLAndroidSimpleBufferQueueItf m_buffer_queue_interface{};
|
||||||
|
SLVolumeItf m_volume_interface{};
|
||||||
|
|
||||||
|
std::array<std::unique_ptr<SampleType[]>, NUM_BUFFERS> m_buffers;
|
||||||
|
u32 m_current_buffer = 0;
|
||||||
|
bool m_paused = true;
|
||||||
|
bool m_buffer_enqueued = false;
|
||||||
|
};
|
|
@ -178,10 +178,20 @@
|
||||||
<item>analog_stick</item>
|
<item>analog_stick</item>
|
||||||
<item>analog_sticks</item>
|
<item>analog_sticks</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
<string-array name="settings_audio_backend_entries">
|
||||||
|
<item>Null (No Output)</item>
|
||||||
|
<item>Cubeb</item>
|
||||||
|
<item>OpenSL ES (Recommended)</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="settings_audio_backend_values">
|
||||||
|
<item>Null</item>
|
||||||
|
<item>Cubeb</item>
|
||||||
|
<item>OpenSLES</item>
|
||||||
|
</string-array>
|
||||||
<string-array name="settings_audio_buffer_size_entries">
|
<string-array name="settings_audio_buffer_size_entries">
|
||||||
<item>1024 Frames (23.22ms)</item>
|
<item>1024 Frames (23.22ms)</item>
|
||||||
<item>2048 Frames (46.44ms)</item>
|
<item>2048 Frames (46.44ms, Recommended)</item>
|
||||||
<item>3072 Frames (69.66ms, Recommended)</item>
|
<item>3072 Frames (69.66ms)</item>
|
||||||
<item>4096 Frames (92.88ms)</item>
|
<item>4096 Frames (92.88ms)</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="settings_audio_buffer_size_values">
|
<string-array name="settings_audio_buffer_size_values">
|
||||||
|
|
|
@ -300,12 +300,20 @@
|
||||||
app:defaultValue="false"
|
app:defaultValue="false"
|
||||||
app:summary="Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable background music in some games."
|
app:summary="Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable background music in some games."
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
<ListPreference
|
||||||
|
app:key="Audio/Backend"
|
||||||
|
app:title="Audio Backend"
|
||||||
|
app:entries="@array/settings_audio_backend_entries"
|
||||||
|
app:entryValues="@array/settings_audio_backend_values"
|
||||||
|
app:defaultValue="OpenSLES"
|
||||||
|
app:useSimpleSummaryProvider="true"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
<ListPreference
|
<ListPreference
|
||||||
app:key="Audio/BufferSize"
|
app:key="Audio/BufferSize"
|
||||||
app:title="Audio Buffer Size"
|
app:title="Audio Buffer Size"
|
||||||
app:entries="@array/settings_audio_buffer_size_entries"
|
app:entries="@array/settings_audio_buffer_size_entries"
|
||||||
app:entryValues="@array/settings_audio_buffer_size_values"
|
app:entryValues="@array/settings_audio_buffer_size_values"
|
||||||
app:defaultValue="3072"
|
app:defaultValue="2048"
|
||||||
app:summary="Determines the latency between audio being generated and output to speakers. Smaller values reduce latency, but variations in emulation speed will cause hitches."
|
app:summary="Determines the latency between audio being generated and output to speakers. Smaller values reduce latency, but variations in emulation speed will cause hitches."
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
|
|
@ -592,10 +592,24 @@ float Settings::GetDisplayAspectRatioValue(DisplayAspectRatio ar)
|
||||||
return s_display_aspect_ratio_values[static_cast<int>(ar)];
|
return s_display_aspect_ratio_values[static_cast<int>(ar)];
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::array<const char*, 3> s_audio_backend_names = {{"Null", "Cubeb", "SDL"}};
|
static std::array<const char*, 3> s_audio_backend_names = {{
|
||||||
static std::array<const char*, 3> s_audio_backend_display_names = {{TRANSLATABLE("AudioBackend", "Null (No Output)"),
|
"Null",
|
||||||
TRANSLATABLE("AudioBackend", "Cubeb"),
|
"Cubeb",
|
||||||
TRANSLATABLE("AudioBackend", "SDL")}};
|
#ifndef ANDROID
|
||||||
|
"SDL",
|
||||||
|
#else
|
||||||
|
"OpenSLES",
|
||||||
|
#endif
|
||||||
|
}};
|
||||||
|
static std::array<const char*, 3> s_audio_backend_display_names = {{
|
||||||
|
TRANSLATABLE("AudioBackend", "Null (No Output)"),
|
||||||
|
TRANSLATABLE("AudioBackend", "Cubeb"),
|
||||||
|
#ifndef ANDROID
|
||||||
|
TRANSLATABLE("AudioBackend", "SDL"),
|
||||||
|
#else
|
||||||
|
TRANSLATABLE("AudioBackend", "OpenSL ES"),
|
||||||
|
#endif
|
||||||
|
}};
|
||||||
|
|
||||||
std::optional<AudioBackend> Settings::ParseAudioBackend(const char* str)
|
std::optional<AudioBackend> Settings::ParseAudioBackend(const char* str)
|
||||||
{
|
{
|
||||||
|
|
|
@ -251,8 +251,15 @@ struct Settings
|
||||||
#endif
|
#endif
|
||||||
static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest;
|
static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest;
|
||||||
static constexpr ConsoleRegion DEFAULT_CONSOLE_REGION = ConsoleRegion::Auto;
|
static constexpr ConsoleRegion DEFAULT_CONSOLE_REGION = ConsoleRegion::Auto;
|
||||||
|
|
||||||
static constexpr CPUExecutionMode DEFAULT_CPU_EXECUTION_MODE = CPUExecutionMode::Recompiler;
|
static constexpr CPUExecutionMode DEFAULT_CPU_EXECUTION_MODE = CPUExecutionMode::Recompiler;
|
||||||
|
|
||||||
|
#ifndef ANDROID
|
||||||
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::Cubeb;
|
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::Cubeb;
|
||||||
|
#else
|
||||||
|
static constexpr AudioBackend DEFAULT_AUDIO_BACKEND = AudioBackend::OpenSLES;
|
||||||
|
#endif
|
||||||
|
|
||||||
static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
|
static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
|
||||||
static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::R4_3;
|
static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::R4_3;
|
||||||
static constexpr ControllerType DEFAULT_CONTROLLER_1_TYPE = ControllerType::DigitalController;
|
static constexpr ControllerType DEFAULT_CONTROLLER_1_TYPE = ControllerType::DigitalController;
|
||||||
|
|
|
@ -97,7 +97,11 @@ enum class AudioBackend : u8
|
||||||
{
|
{
|
||||||
Null,
|
Null,
|
||||||
Cubeb,
|
Cubeb,
|
||||||
|
#ifndef ANDROID
|
||||||
SDL,
|
SDL,
|
||||||
|
#else
|
||||||
|
OpenSLES,
|
||||||
|
#endif
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue