mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-04-24 14:24:54 +00:00
Merge 489d0366d9
into 3025cd472c
This commit is contained in:
commit
40260def48
5 changed files with 156 additions and 70 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<jclass>(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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,4 +117,8 @@ jfieldID GetInputDetectorPointer();
|
|||
|
||||
jmethodID GetRunnableRun();
|
||||
|
||||
jclass GetAudioUtilsClass();
|
||||
jmethodID GetAudioUtilsGetSampleRate();
|
||||
jmethodID GetAudioUtilsGetFramesPerBuffer();
|
||||
|
||||
} // namespace IDCache
|
||||
|
|
|
@ -8,40 +8,27 @@
|
|||
|
||||
#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"
|
||||
|
||||
// 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<OpenSLESStream*>(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<short*>(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<short>& 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<SLmillibel>(2000 * std::log10(volume / 100.0f));
|
||||
(*bqPlayerVolume)->SetVolumeLevel(bqPlayerVolume, attenuation);
|
||||
(*m_bq_player_volume)->SetVolumeLevel(m_bq_player_volume, attenuation);
|
||||
}
|
||||
#endif // HAVE_OPENSL_ES
|
||||
|
|
|
@ -3,10 +3,15 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#ifdef HAVE_OPENSL_ES
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#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<std::vector<short>, 2> m_buffer;
|
||||
int m_current_buffer = 0;
|
||||
#endif // HAVE_OPENSL_ES
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue