From 05bfc896db1555588a3bdea80b30215eb518ae9f Mon Sep 17 00:00:00 2001 From: Andreas Pehrson Date: Thu, 13 Jun 2024 23:51:39 +0200 Subject: Implement JNI layer cubeb_fx for controlling android audiofx classes --- src/cubeb-jni.cpp | 199 +++++++++++++++++++++++++++++++++++++++++++++--------- src/cubeb-jni.h | 18 +++++ 2 files changed, 185 insertions(+), 32 deletions(-) diff --git a/src/cubeb-jni.cpp b/src/cubeb-jni.cpp index 8e7345b..57c642d 100644 --- a/src/cubeb-jni.cpp +++ b/src/cubeb-jni.cpp @@ -1,7 +1,10 @@ /* clang-format off */ -#include "jni.h" -#include +#include "cubeb-jni.h" #include "cubeb-jni-instances.h" +#include +#include +#include +#include /* clang-format on */ #define AUDIO_STREAM_TYPE_MUSIC 3 @@ -9,7 +12,53 @@ struct cubeb_jni { jobject s_audio_manager_obj = nullptr; jclass s_audio_manager_class = nullptr; - jmethodID s_get_output_latency_id = nullptr; + jmethodID s_audio_manager_get_output_latency_id = nullptr; + struct cubeb_fx_def { + jclass clazz = nullptr; + jmethodID static_function_is_available = nullptr; + jmethodID static_function_create = nullptr; + jmethodID method_set_enabled = nullptr; + jmethodID method_release = nullptr; + } s_fxs[CUBEB_FX_SENTINEL__]{}; + std::atomic cubeb_fx_count{0}; +}; + +static constexpr const char * +cubeb_fx_name(cubeb_fx_type type) +{ + switch (type) { + case CUBEB_FX_ACOUSTIC_ECHO_CANCELER: + return "android/media/audiofx/AcousticEchoCanceler"; + case CUBEB_FX_AUTOMATIC_GAIN_CONTROL: + return "android/media/audiofx/AutomaticGainControl"; + case CUBEB_FX_NOISE_SUPPRESSOR: + return "android/media/audiofx/NoiseSuppressor"; + case CUBEB_FX_SENTINEL__: + return nullptr; + } + return nullptr; +} + +static constexpr const char * +cubeb_fx_create_signature(cubeb_fx_type type) +{ + switch (type) { + case CUBEB_FX_ACOUSTIC_ECHO_CANCELER: + return "(I)Landroid/media/audiofx/AcousticEchoCanceler;"; + case CUBEB_FX_AUTOMATIC_GAIN_CONTROL: + return "(I)Landroid/media/audiofx/AutomaticGainControl;"; + case CUBEB_FX_NOISE_SUPPRESSOR: + return "(I)Landroid/media/audiofx/NoiseSuppressor;"; + case CUBEB_FX_SENTINEL__: + return nullptr; + } + return nullptr; +} + +struct cubeb_fx { + cubeb_jni * jni = nullptr; + cubeb_fx_type type = CUBEB_FX_SENTINEL__; + jobject obj = nullptr; }; extern "C" cubeb_jni * @@ -24,38 +73,120 @@ cubeb_jni_init() cubeb_jni * cubeb_jni_ptr = new cubeb_jni; assert(cubeb_jni_ptr); - // Find the audio manager object and make it global to call it from another - // method - jclass context_class = jni_env->FindClass("android/content/Context"); - jfieldID audio_service_field = jni_env->GetStaticFieldID( - context_class, "AUDIO_SERVICE", "Ljava/lang/String;"); - jstring jstr = (jstring)jni_env->GetStaticObjectField(context_class, - audio_service_field); - jmethodID get_system_service_id = - jni_env->GetMethodID(context_class, "getSystemService", - "(Ljava/lang/String;)Ljava/lang/Object;"); - jobject audio_manager_obj = - jni_env->CallObjectMethod(ctx_obj, get_system_service_id, jstr); - cubeb_jni_ptr->s_audio_manager_obj = - reinterpret_cast(jni_env->NewGlobalRef(audio_manager_obj)); - - // Make the audio manager class a global reference in order to preserve method - // id - jclass audio_manager_class = jni_env->FindClass("android/media/AudioManager"); - cubeb_jni_ptr->s_audio_manager_class = - reinterpret_cast(jni_env->NewGlobalRef(audio_manager_class)); - cubeb_jni_ptr->s_get_output_latency_id = - jni_env->GetMethodID(audio_manager_class, "getOutputLatency", "(I)I"); - - jni_env->DeleteLocalRef(ctx_obj); - jni_env->DeleteLocalRef(context_class); - jni_env->DeleteLocalRef(jstr); - jni_env->DeleteLocalRef(audio_manager_obj); - jni_env->DeleteLocalRef(audio_manager_class); + { + // Find the audio manager object and make it global to call it from another + // method + jclass context_class = jni_env->FindClass("android/content/Context"); + jfieldID audio_service_field = jni_env->GetStaticFieldID( + context_class, "AUDIO_SERVICE", "Ljava/lang/String;"); + jstring jstr = (jstring)jni_env->GetStaticObjectField(context_class, + audio_service_field); + jmethodID get_system_service_id = + jni_env->GetMethodID(context_class, "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;"); + jobject audio_manager_obj = + jni_env->CallObjectMethod(ctx_obj, get_system_service_id, jstr); + cubeb_jni_ptr->s_audio_manager_obj = + reinterpret_cast(jni_env->NewGlobalRef(audio_manager_obj)); + jni_env->DeleteLocalRef(ctx_obj); + jni_env->DeleteLocalRef(context_class); + jni_env->DeleteLocalRef(jstr); + jni_env->DeleteLocalRef(audio_manager_obj); + } + + { + // Make the audio manager class a global reference in order to preserve + // method id + jclass audio_manager_class = + jni_env->FindClass("android/media/AudioManager"); + cubeb_jni_ptr->s_audio_manager_class = + reinterpret_cast(jni_env->NewGlobalRef(audio_manager_class)); + cubeb_jni_ptr->s_audio_manager_get_output_latency_id = + jni_env->GetMethodID(audio_manager_class, "getOutputLatency", "(I)I"); + jni_env->DeleteLocalRef(audio_manager_class); + } + + auto populate_fx_class = [&](cubeb_fx_type type) { + jclass c = jni_env->FindClass(cubeb_fx_name(type)); + cubeb_jni::cubeb_fx_def & fx = cubeb_jni_ptr->s_fxs[type]; + fx.clazz = reinterpret_cast(jni_env->NewGlobalRef(c)); + fx.static_function_is_available = + jni_env->GetStaticMethodID(c, "isAvailable", "()Z"); + fx.static_function_create = jni_env->GetStaticMethodID( + c, "create", cubeb_fx_create_signature(type)); + fx.method_set_enabled = jni_env->GetMethodID(c, "setEnabled", "(Z)I"); + fx.method_release = jni_env->GetMethodID(c, "release", "()V"); + jni_env->DeleteLocalRef(c); + }; + populate_fx_class(CUBEB_FX_ACOUSTIC_ECHO_CANCELER); + populate_fx_class(CUBEB_FX_AUTOMATIC_GAIN_CONTROL); + populate_fx_class(CUBEB_FX_NOISE_SUPPRESSOR); return cubeb_jni_ptr; } +extern "C" bool +cubeb_fx_is_available(cubeb_jni * cubeb_jni_ptr, cubeb_fx_type type) +{ + JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); + cubeb_jni::cubeb_fx_def & fx = cubeb_jni_ptr->s_fxs[type]; + return JNI_FALSE != jni_env->CallStaticBooleanMethod( + fx.clazz, fx.static_function_is_available); +} + +extern "C" cubeb_fx * +cubeb_fx_init(cubeb_jni * cubeb_jni_ptr, cubeb_fx_type type, int audio_session) +{ + JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); + cubeb_jni::cubeb_fx_def & fx = cubeb_jni_ptr->s_fxs[type]; + cubeb_jni_ptr->cubeb_fx_count += 1; + + jobject obj = jni_env->CallStaticObjectMethod( + fx.clazz, fx.static_function_create, static_cast(audio_session)); + if (!obj) { + return nullptr; + } + + cubeb_fx * res = new cubeb_fx; + res->jni = cubeb_jni_ptr; + res->type = type; + res->obj = reinterpret_cast(jni_env->NewGlobalRef(obj)); + + jni_env->DeleteLocalRef(obj); + + return res; +} + +extern "C" int +cubeb_fx_set_enabled(cubeb_fx * cubeb_fx_ptr, bool enabled) +{ + assert(cubeb_fx_ptr); + + JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); + cubeb_jni::cubeb_fx_def & fx = cubeb_fx_ptr->jni->s_fxs[cubeb_fx_ptr->type]; + return static_cast( + jni_env->CallIntMethod(cubeb_fx_ptr->obj, fx.method_set_enabled, + static_cast(enabled))); +} + +extern "C" void +cubeb_fx_destroy(cubeb_fx * cubeb_fx_ptr) +{ + assert(cubeb_fx_ptr); + + JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); + + { + cubeb_jni::cubeb_fx_def & fx = cubeb_fx_ptr->jni->s_fxs[cubeb_fx_ptr->type]; + jni_env->CallVoidMethod(cubeb_fx_ptr->obj, fx.method_release); + } + + jni_env->DeleteGlobalRef(cubeb_fx_ptr->obj); + + cubeb_fx_ptr->jni->cubeb_fx_count -= 1; + delete cubeb_fx_ptr; +} + extern "C" int cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr) { @@ -63,7 +194,7 @@ cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr) JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); return jni_env->CallIntMethod( cubeb_jni_ptr->s_audio_manager_obj, - cubeb_jni_ptr->s_get_output_latency_id, + cubeb_jni_ptr->s_audio_manager_get_output_latency_id, AUDIO_STREAM_TYPE_MUSIC); // param: AudioManager.STREAM_MUSIC } @@ -71,12 +202,16 @@ extern "C" void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr) { assert(cubeb_jni_ptr); + assert(cubeb_jni_ptr->cubeb_fx_count == 0); JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); assert(jni_env); jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_obj); jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_class); + for (size_t i = 0; i < CUBEB_FX_SENTINEL__; ++i) { + jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_fxs[i].clazz); + } delete cubeb_jni_ptr; } diff --git a/src/cubeb-jni.h b/src/cubeb-jni.h index d63629f..0a2d41a 100644 --- a/src/cubeb-jni.h +++ b/src/cubeb-jni.h @@ -2,6 +2,13 @@ #define _CUBEB_JNI_H_ typedef struct cubeb_jni cubeb_jni; +typedef struct cubeb_fx cubeb_fx; +enum cubeb_fx_type { + CUBEB_FX_ACOUSTIC_ECHO_CANCELER, + CUBEB_FX_AUTOMATIC_GAIN_CONTROL, + CUBEB_FX_NOISE_SUPPRESSOR, + CUBEB_FX_SENTINEL__, +}; #ifdef __cplusplus extern "C" { @@ -9,8 +16,19 @@ extern "C" { cubeb_jni * cubeb_jni_init(); + +bool +cubeb_fx_is_available(cubeb_jni * cubeb_jni_ptr, cubeb_fx_type type); +cubeb_fx * +cubeb_fx_init(cubeb_jni * cubeb_jni_ptr, cubeb_fx_type type, int audio_session); +int +cubeb_fx_set_enabled(cubeb_fx * cubeb_fx_ptr, bool enabled); +void +cubeb_fx_destroy(cubeb_fx * cubeb_fx_ptr); + int cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr); + void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr); -- cgit v1.2.3