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 ed382745c0..9bf016f3a1 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -118,6 +118,10 @@ static jfieldID s_input_detector_pointer; static jmethodID s_runnable_run; +static jclass s_audio_utils_class; +static jmethodID s_audio_utils_get_sample_rate; +static jmethodID s_audio_utils_get_frames_per_buffer; + namespace IDCache { JNIEnv* GetEnvForThread() @@ -543,6 +547,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" { @@ -769,6 +788,13 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) s_runnable_run = env->GetMethodID(runnable_class, "run", "()V"); env->DeleteLocalRef(runnable_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); + return JNI_VERSION; } @@ -804,5 +830,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) env->DeleteGlobalRef(s_core_device_class); env->DeleteGlobalRef(s_core_device_control_class); env->DeleteGlobalRef(s_input_detector_class); + env->DeleteGlobalRef(s_audio_utils_class); } } diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h index 0b01d14b42..572b305777 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.h +++ b/Source/Android/jni/AndroidCommon/IDCache.h @@ -117,4 +117,8 @@ jfieldID GetInputDetectorPointer(); 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 a3f6308187..3fc71e75df 100644 --- a/Source/Core/AudioCommon/OpenSLESStream.cpp +++ b/Source/Core/AudioCommon/OpenSLESStream.cpp @@ -8,40 +8,27 @@ #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" -// engine interfaces -static SLObjectItf engineObject; -static SLEngineItf engineEngine; -static SLObjectItf outputMixObject; - -// buffer queue player interfaces -static SLObjectItf bqPlayerObject = nullptr; -static SLPlayItf bqPlayerPlay; -static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; -static SLVolumeItf bqPlayerVolume; -static Mixer* g_mixer; -#define BUFFER_SIZE 512 -#define BUFFER_SIZE_IN_SAMPLES (BUFFER_SIZE / 2) - -// Double buffering. -static short buffer[2][BUFFER_SIZE]; -static int curBuffer = 0; - -static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context) +void OpenSLESStream::BQPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context) { - ASSERT(bq == bqPlayerBufferQueue); - ASSERT(nullptr == context); + reinterpret_cast(context)->PushSamples(bq); +} + +void OpenSLESStream::PushSamples(SLAndroidSimpleBufferQueueItf bq) +{ + ASSERT(bq == m_bq_player_buffer_queue); // Render to the fresh buffer - g_mixer->Mix(reinterpret_cast(buffer[curBuffer]), BUFFER_SIZE_IN_SAMPLES); - SLresult result = - (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[curBuffer], sizeof(buffer[0])); - curBuffer ^= 1; // Switch 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, @@ -51,61 +38,78 @@ static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context) 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(&engineObject, 0, nullptr, 0, nullptr, nullptr); + result = slCreateEngine(&m_engine_object, 0, nullptr, 0, nullptr, nullptr); ASSERT(SL_RESULT_SUCCESS == result); - result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); + result = (*m_engine_object)->Realize(m_engine_object, SL_BOOLEAN_FALSE); ASSERT(SL_RESULT_SUCCESS == result); - result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); + result = (*m_engine_object)->GetInterface(m_engine_object, SL_IID_ENGINE, &m_engine_engine); ASSERT(SL_RESULT_SUCCESS == result); - result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0); + result = (*m_engine_engine)->CreateOutputMix(m_engine_engine, &m_output_mix_object, 0, 0, 0); ASSERT(SL_RESULT_SUCCESS == result); - result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); + 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, - 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}; // configure audio sink - SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; + 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 = - (*engineEngine) - ->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req); + result = (*m_engine_engine) + ->CreateAudioPlayer(m_engine_engine, &m_bq_player_object, &audioSrc, &audioSnk, 2, + ids, req); ASSERT(SL_RESULT_SUCCESS == result); - result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); + result = (*m_bq_player_object)->Realize(m_bq_player_object, SL_BOOLEAN_FALSE); ASSERT(SL_RESULT_SUCCESS == result); - result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); + 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 = - (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue); + (*m_bq_player_object)->GetInterface(m_bq_player_object, SL_IID_VOLUME, &m_bq_player_volume); ASSERT(SL_RESULT_SUCCESS == result); - result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); + result = (*m_bq_player_buffer_queue) + ->RegisterCallback(m_bq_player_buffer_queue, BQPlayerCallback, this); ASSERT(SL_RESULT_SUCCESS == result); - result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, nullptr); - ASSERT(SL_RESULT_SUCCESS == result); - result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); + result = (*m_bq_player_play)->SetPlayState(m_bq_player_play, SL_PLAYSTATE_PLAYING); ASSERT(SL_RESULT_SUCCESS == result); // Render and enqueue a first buffer. - curBuffer ^= 1; - g_mixer = m_mixer.get(); + m_current_buffer ^= 1; - result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[0], sizeof(buffer[0])); + 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; @@ -114,39 +118,39 @@ bool OpenSLESStream::Init() OpenSLESStream::~OpenSLESStream() { - if (bqPlayerObject != nullptr) + if (m_bq_player_object != nullptr) { - (*bqPlayerObject)->Destroy(bqPlayerObject); - bqPlayerObject = nullptr; - bqPlayerPlay = nullptr; - bqPlayerBufferQueue = nullptr; - bqPlayerVolume = 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 (outputMixObject != nullptr) + if (m_output_mix_object != nullptr) { - (*outputMixObject)->Destroy(outputMixObject); - outputMixObject = nullptr; + (*m_output_mix_object)->Destroy(m_output_mix_object); + m_output_mix_object = nullptr; } - if (engineObject != nullptr) + if (m_engine_object != nullptr) { - (*engineObject)->Destroy(engineObject); - engineObject = nullptr; - engineEngine = 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 (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, new_state) == SL_RESULT_SUCCESS; + 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(2000 * std::log10(volume / 100.0f)); - (*bqPlayerVolume)->SetVolumeLevel(bqPlayerVolume, attenuation); + (*m_bq_player_volume)->SetVolumeLevel(m_bq_player_volume, attenuation); } #endif // HAVE_OPENSL_ES diff --git a/Source/Core/AudioCommon/OpenSLESStream.h b/Source/Core/AudioCommon/OpenSLESStream.h index f22aaf9a0f..9772f0b9c7 100644 --- a/Source/Core/AudioCommon/OpenSLESStream.h +++ b/Source/Core/AudioCommon/OpenSLESStream.h @@ -3,10 +3,15 @@ #pragma once -#include +#ifdef HAVE_OPENSL_ES +#include +#include + +#include +#include +#endif // HAVE_OPENSL_ES #include "AudioCommon/SoundStream.h" -#include "Common/Event.h" class OpenSLESStream final : public SoundStream { @@ -19,7 +24,25 @@ public: static bool IsValid() { return true; } private: - std::thread thread; - Common::Event soundSyncEvent; + static void BQPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context); + void PushSamples(SLAndroidSimpleBufferQueueItf bq); + + // engine interfaces + SLObjectItf m_engine_object; + SLEngineItf m_engine_engine; + SLObjectItf m_output_mix_object; + + // buffer queue player interfaces + SLObjectItf m_bq_player_object = nullptr; + SLPlayItf m_bq_player_play; + SLAndroidSimpleBufferQueueItf m_bq_player_buffer_queue; + SLVolumeItf m_bq_player_volume; + + SLuint32 m_frames_per_buffer; + SLuint32 m_bytes_per_buffer; + + // Double buffering. + std::array, 2> m_buffer; + int m_current_buffer = 0; #endif // HAVE_OPENSL_ES };