mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-04-24 22:34:54 +00:00
This can reduce audio latency according to https://developer.android.com/ndk/guides/audio/opensl/opensl-prog-notes#perform. Previously we were using the hardcoded values of 48000 Hz and 256 frames per buffer. The sample rate we use with this change is 48000 Hz on all devices I'm aware of, but the buffer size does vary across devices. Terminology note: The old code used the term "sample" to refer to what Android refers to as a "frame". "Frame" is a clearer term to use for this, so I've changed OpenSLESStream's terminology. One frame consists of one sample per channel.
156 lines
5.6 KiB
C++
156 lines
5.6 KiB
C++
// Copyright 2013 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#ifdef HAVE_OPENSL_ES
|
|
#include "AudioCommon/OpenSLESStream.h"
|
|
|
|
#include <cmath>
|
|
|
|
#include <SLES/OpenSLES.h>
|
|
#include <SLES/OpenSLES_Android.h>
|
|
#include <jni.h>
|
|
|
|
#include "Common/Assert.h"
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Logging/Log.h"
|
|
#include "Core/ConfigManager.h"
|
|
#include "jni/AndroidCommon/IDCache.h"
|
|
|
|
void OpenSLESStream::BQPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
|
|
{
|
|
reinterpret_cast<OpenSLESStream*>(context)->PushSamples(bq);
|
|
}
|
|
|
|
void OpenSLESStream::PushSamples(SLAndroidSimpleBufferQueueItf bq)
|
|
{
|
|
ASSERT(bq == m_bq_player_buffer_queue);
|
|
|
|
// Render to the fresh buffer
|
|
m_mixer->Mix(m_buffer[m_current_buffer].data(), m_frames_per_buffer);
|
|
SLresult result = (*bq)->Enqueue(bq, m_buffer[m_current_buffer].data(), m_bytes_per_buffer);
|
|
m_current_buffer ^= 1; // Switch buffer
|
|
|
|
// Comment from sample code:
|
|
// the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
|
|
// which for this code example would indicate a programming error
|
|
ASSERT_MSG(AUDIO, SL_RESULT_SUCCESS == result, "Couldn't enqueue audio stream.");
|
|
}
|
|
|
|
bool OpenSLESStream::Init()
|
|
{
|
|
JNIEnv* env = IDCache::GetEnvForThread();
|
|
jclass audio_utils = IDCache::GetAudioUtilsClass();
|
|
const SLuint32 sample_rate =
|
|
env->CallStaticIntMethod(audio_utils, IDCache::GetAudioUtilsGetSampleRate());
|
|
m_frames_per_buffer =
|
|
env->CallStaticIntMethod(audio_utils, IDCache::GetAudioUtilsGetFramesPerBuffer());
|
|
|
|
INFO_LOG_FMT(AUDIO, "OpenSLES configuration: {} Hz, {} frames per buffer", sample_rate,
|
|
m_frames_per_buffer);
|
|
|
|
constexpr SLuint32 channels = 2;
|
|
const SLuint32 samples_per_buffer = m_frames_per_buffer * channels;
|
|
m_bytes_per_buffer = m_frames_per_buffer * channels * sizeof(m_buffer[0][0]);
|
|
|
|
for (std::vector<short>& buffer : m_buffer)
|
|
buffer.resize(samples_per_buffer);
|
|
|
|
SLresult result;
|
|
// create engine
|
|
result = slCreateEngine(&m_engine_object, 0, nullptr, 0, nullptr, nullptr);
|
|
ASSERT(SL_RESULT_SUCCESS == result);
|
|
result = (*m_engine_object)->Realize(m_engine_object, SL_BOOLEAN_FALSE);
|
|
ASSERT(SL_RESULT_SUCCESS == result);
|
|
result = (*m_engine_object)->GetInterface(m_engine_object, SL_IID_ENGINE, &m_engine_engine);
|
|
ASSERT(SL_RESULT_SUCCESS == result);
|
|
result = (*m_engine_engine)->CreateOutputMix(m_engine_engine, &m_output_mix_object, 0, 0, 0);
|
|
ASSERT(SL_RESULT_SUCCESS == result);
|
|
result = (*m_output_mix_object)->Realize(m_output_mix_object, SL_BOOLEAN_FALSE);
|
|
ASSERT(SL_RESULT_SUCCESS == result);
|
|
|
|
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
|
|
SLDataFormat_PCM format_pcm = {
|
|
SL_DATAFORMAT_PCM, channels,
|
|
sample_rate * 1000, SL_PCMSAMPLEFORMAT_FIXED_16,
|
|
SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
|
SL_BYTEORDER_LITTLEENDIAN};
|
|
|
|
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
|
|
|
|
// configure audio sink
|
|
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, m_output_mix_object};
|
|
SLDataSink audioSnk = {&loc_outmix, nullptr};
|
|
|
|
// create audio player
|
|
const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
|
|
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
|
result = (*m_engine_engine)
|
|
->CreateAudioPlayer(m_engine_engine, &m_bq_player_object, &audioSrc, &audioSnk, 2,
|
|
ids, req);
|
|
ASSERT(SL_RESULT_SUCCESS == result);
|
|
|
|
result = (*m_bq_player_object)->Realize(m_bq_player_object, SL_BOOLEAN_FALSE);
|
|
ASSERT(SL_RESULT_SUCCESS == result);
|
|
result = (*m_bq_player_object)->GetInterface(m_bq_player_object, SL_IID_PLAY, &m_bq_player_play);
|
|
ASSERT(SL_RESULT_SUCCESS == result);
|
|
result = (*m_bq_player_object)
|
|
->GetInterface(m_bq_player_object, SL_IID_BUFFERQUEUE, &m_bq_player_buffer_queue);
|
|
ASSERT(SL_RESULT_SUCCESS == result);
|
|
result =
|
|
(*m_bq_player_object)->GetInterface(m_bq_player_object, SL_IID_VOLUME, &m_bq_player_volume);
|
|
ASSERT(SL_RESULT_SUCCESS == result);
|
|
result = (*m_bq_player_buffer_queue)
|
|
->RegisterCallback(m_bq_player_buffer_queue, BQPlayerCallback, this);
|
|
ASSERT(SL_RESULT_SUCCESS == result);
|
|
result = (*m_bq_player_play)->SetPlayState(m_bq_player_play, SL_PLAYSTATE_PLAYING);
|
|
ASSERT(SL_RESULT_SUCCESS == result);
|
|
|
|
// Render and enqueue a first buffer.
|
|
m_current_buffer ^= 1;
|
|
|
|
result = (*m_bq_player_buffer_queue)
|
|
->Enqueue(m_bq_player_buffer_queue, m_buffer[0].data(), m_bytes_per_buffer);
|
|
if (SL_RESULT_SUCCESS != result)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
OpenSLESStream::~OpenSLESStream()
|
|
{
|
|
if (m_bq_player_object != nullptr)
|
|
{
|
|
(*m_bq_player_object)->Destroy(m_bq_player_object);
|
|
m_bq_player_object = nullptr;
|
|
m_bq_player_play = nullptr;
|
|
m_bq_player_buffer_queue = nullptr;
|
|
m_bq_player_volume = nullptr;
|
|
}
|
|
|
|
if (m_output_mix_object != nullptr)
|
|
{
|
|
(*m_output_mix_object)->Destroy(m_output_mix_object);
|
|
m_output_mix_object = nullptr;
|
|
}
|
|
|
|
if (m_engine_object != nullptr)
|
|
{
|
|
(*m_engine_object)->Destroy(m_engine_object);
|
|
m_engine_object = nullptr;
|
|
m_engine_engine = nullptr;
|
|
}
|
|
}
|
|
|
|
bool OpenSLESStream::SetRunning(bool running)
|
|
{
|
|
SLuint32 new_state = running ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_PAUSED;
|
|
return (*m_bq_player_play)->SetPlayState(m_bq_player_play, new_state) == SL_RESULT_SUCCESS;
|
|
}
|
|
|
|
void OpenSLESStream::SetVolume(int volume)
|
|
{
|
|
const SLmillibel attenuation =
|
|
volume <= 0 ? SL_MILLIBEL_MIN : static_cast<SLmillibel>(2000 * std::log10(volume / 100.0f));
|
|
(*m_bq_player_volume)->SetVolumeLevel(m_bq_player_volume, attenuation);
|
|
}
|
|
#endif // HAVE_OPENSL_ES
|