diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AudioUtils.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AudioUtils.kt new file mode 100644 index 0000000000..68e3573fa5 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AudioUtils.kt @@ -0,0 +1,28 @@ +package org.dolphinemu.dolphinemu.utils + +import android.content.Context +import android.media.AudioManager +import androidx.annotation.Keep +import org.dolphinemu.dolphinemu.DolphinApplication + +object AudioUtils { + @JvmStatic @Keep + fun getSampleRate(): Int = + getAudioServiceProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE, 48000) + + @JvmStatic @Keep + fun getFramesPerBuffer(): Int = + getAudioServiceProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER, 256) + + private fun getAudioServiceProperty(property: String, fallback: Int): Int { + return try { + val context = DolphinApplication.getAppContext() + val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + Integer.parseUnsignedInt(am.getProperty(property)) + } catch (e: NullPointerException) { + fallback + } catch (e: NumberFormatException) { + fallback + } + } +} diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 5fe151278f..4fc200b246 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -110,6 +110,10 @@ static jclass s_core_device_control_class; static jfieldID s_core_device_control_pointer; static jmethodID s_core_device_control_constructor; +static jclass s_audio_utils_class; +static jmethodID s_audio_utils_get_sample_rate; +static jmethodID s_audio_utils_get_frames_per_buffer; + static jmethodID s_runnable_run; namespace IDCache @@ -517,6 +521,21 @@ jmethodID GetRunnableRun() return s_runnable_run; } +jclass GetAudioUtilsClass() +{ + return s_audio_utils_class; +} + +jmethodID GetAudioUtilsGetSampleRate() +{ + return s_audio_utils_get_sample_rate; +} + +jmethodID GetAudioUtilsGetFramesPerBuffer() +{ + return s_audio_utils_get_frames_per_buffer; +} + } // namespace IDCache extern "C" { @@ -724,6 +743,13 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) "(Lorg/dolphinemu/dolphinemu/features/input/model/CoreDevice;J)V"); env->DeleteLocalRef(core_device_control_class); + const jclass audio_utils_class = env->FindClass("org/dolphinemu/dolphinemu/utils/AudioUtils"); + s_audio_utils_class = reinterpret_cast(env->NewGlobalRef(audio_utils_class)); + s_audio_utils_get_sample_rate = env->GetStaticMethodID(audio_utils_class, "getSampleRate", "()I"); + s_audio_utils_get_frames_per_buffer = + env->GetStaticMethodID(audio_utils_class, "getFramesPerBuffer", "()I"); + env->DeleteLocalRef(audio_utils_class); + const jclass runnable_class = env->FindClass("java/lang/Runnable"); s_runnable_run = env->GetMethodID(runnable_class, "run", "()V"); env->DeleteLocalRef(runnable_class); @@ -761,5 +787,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) env->DeleteGlobalRef(s_numeric_setting_class); env->DeleteGlobalRef(s_core_device_class); env->DeleteGlobalRef(s_core_device_control_class); + env->DeleteGlobalRef(s_audio_utils_class); } } diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h index c324b6cb19..97cac1cc93 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.h +++ b/Source/Android/jni/AndroidCommon/IDCache.h @@ -111,4 +111,8 @@ jmethodID GetCoreDeviceControlConstructor(); jmethodID GetRunnableRun(); +jclass GetAudioUtilsClass(); +jmethodID GetAudioUtilsGetSampleRate(); +jmethodID GetAudioUtilsGetFramesPerBuffer(); + } // namespace IDCache diff --git a/Source/Core/AudioCommon/OpenSLESStream.cpp b/Source/Core/AudioCommon/OpenSLESStream.cpp index 84831ceec6..3fc71e75df 100644 --- a/Source/Core/AudioCommon/OpenSLESStream.cpp +++ b/Source/Core/AudioCommon/OpenSLESStream.cpp @@ -8,11 +8,13 @@ #include #include +#include #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) { @@ -24,8 +26,8 @@ void OpenSLESStream::PushSamples(SLAndroidSimpleBufferQueueItf bq) ASSERT(bq == m_bq_player_buffer_queue); // Render to the fresh buffer - m_mixer->Mix(reinterpret_cast(m_buffer[m_current_buffer]), BUFFER_SIZE_IN_SAMPLES); - SLresult result = (*bq)->Enqueue(bq, m_buffer[m_current_buffer], sizeof(m_buffer[0])); + 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: @@ -36,6 +38,23 @@ void OpenSLESStream::PushSamples(SLAndroidSimpleBufferQueueItf bq) 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& buffer : m_buffer) + buffer.resize(samples_per_buffer); + SLresult result; // create engine result = slCreateEngine(&m_engine_object, 0, nullptr, 0, nullptr, nullptr); @@ -50,13 +69,11 @@ bool OpenSLESStream::Init() ASSERT(SL_RESULT_SUCCESS == result); SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; - SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, - 2, - m_mixer->GetSampleRate() * 1000, - SL_PCMSAMPLEFORMAT_FIXED_16, - SL_PCMSAMPLEFORMAT_FIXED_16, - SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, - SL_BYTEORDER_LITTLEENDIAN}; + 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}; @@ -92,7 +109,7 @@ bool OpenSLESStream::Init() m_current_buffer ^= 1; result = (*m_bq_player_buffer_queue) - ->Enqueue(m_bq_player_buffer_queue, m_buffer[0], sizeof(m_buffer[0])); + ->Enqueue(m_bq_player_buffer_queue, m_buffer[0].data(), m_bytes_per_buffer); if (SL_RESULT_SUCCESS != result) return false; diff --git a/Source/Core/AudioCommon/OpenSLESStream.h b/Source/Core/AudioCommon/OpenSLESStream.h index 009d8d891a..9772f0b9c7 100644 --- a/Source/Core/AudioCommon/OpenSLESStream.h +++ b/Source/Core/AudioCommon/OpenSLESStream.h @@ -4,6 +4,9 @@ #pragma once #ifdef HAVE_OPENSL_ES +#include +#include + #include #include #endif // HAVE_OPENSL_ES @@ -35,11 +38,11 @@ private: SLAndroidSimpleBufferQueueItf m_bq_player_buffer_queue; SLVolumeItf m_bq_player_volume; - static constexpr int BUFFER_SIZE = 512; - static constexpr int BUFFER_SIZE_IN_SAMPLES = BUFFER_SIZE / 2; + SLuint32 m_frames_per_buffer; + SLuint32 m_bytes_per_buffer; // Double buffering. - short m_buffer[2][BUFFER_SIZE]; + std::array, 2> m_buffer; int m_current_buffer = 0; #endif // HAVE_OPENSL_ES };