Bug 1410456 - get mixer latency from JNI instead of system library. r?padenot, jchen draft
authorAlex Chronopoulos <achronop@gmail.com>
Tue, 20 Feb 2018 15:37:12 +0200
changeset 758415 14735f6cd5452ece842537474998fbc3108bd6f3
parent 758414 599f1cec68c592e0cd33a82ebd8e32b32bf198b6
child 758416 e6ff3aa4a1b1d8f3ef953a09e9e73efa179c0a92
child 759681 0dbf1f45a6adf863c677de5218d6f8ed1d2cd7e7
child 760530 177447e9c70d74842ab2c7f7d0c444308a29a46a
child 760842 3ba309225793cbab63686259ebae8249693f76a1
push id100043
push userachronop@gmail.com
push dateThu, 22 Feb 2018 11:35:08 +0000
reviewerspadenot, jchen
bugs1410456
milestone60.0a1
Bug 1410456 - get mixer latency from JNI instead of system library. r?padenot, jchen MozReview-Commit-ID: 3YciuVu25JO
media/libcubeb/src/cubeb-jni-instances.h
media/libcubeb/src/cubeb-jni.cpp
media/libcubeb/src/cubeb-jni.h
media/libcubeb/src/cubeb_opensl.c
media/libcubeb/src/moz.build
new file mode 100644
--- /dev/null
+++ b/media/libcubeb/src/cubeb-jni-instances.h
@@ -0,0 +1,34 @@
+#ifndef _CUBEB_JNI_INSTANCES_H_
+#define _CUBEB_JNI_INSTANCES_H_
+
+#include "GeneratedJNIWrappers.h"
+#include "mozilla/jni/Utils.h"
+
+/*
+ * The methods in this file offer a way to pass in the required
+ * JNI instances in the cubeb library. By default they return NULL.
+ * In this case part of the cubeb API that depends on JNI
+ * will return CUBEB_ERROR_NOT_SUPPORTED. Currently only one
+ * method depends on that:
+ *
+ * cubeb_stream_get_position()
+ *
+ * Users that want to use that cubeb API method must "override"
+ * the methods bellow to return a valid instance of JavaVM
+ * and application's Context object.
+ * */
+
+JavaVM *
+cubeb_jni_get_java_vm()
+{
+  return mozilla::jni::GetVM();
+}
+
+jobject
+cubeb_jni_get_context_instance()
+{
+  auto context = mozilla::java::GeckoAppShell::GetApplicationContext();
+  return context.Forget();
+}
+
+#endif //_CUBEB_JNI_INSTANCES_H_
new file mode 100644
--- /dev/null
+++ b/media/libcubeb/src/cubeb-jni.cpp
@@ -0,0 +1,88 @@
+#include "jni.h"
+#include <assert.h>
+#include "cubeb-jni-instances.h"
+
+#define AUDIO_STREAM_TYPE_MUSIC 3
+
+JNIEnv *
+cubeb_jni_get_env_for_thread(JavaVM * java_vm)
+{
+    JNIEnv * env = nullptr;
+    if (!java_vm->AttachCurrentThread(&env, nullptr)) {
+        assert(env);
+        return env;
+    }
+
+    assert(false && "Failed to get JNIEnv for thread");
+    return nullptr; // unreachable
+}
+
+struct cubeb_jni {
+  JavaVM * s_java_vm = nullptr;
+  jobject s_audio_manager_obj = nullptr;
+  jclass s_audio_manager_class = nullptr;
+  jmethodID s_get_output_latency_id = nullptr;
+};
+
+extern "C"
+cubeb_jni *
+cubeb_jni_init()
+{
+  JavaVM * javaVM = cubeb_jni_get_java_vm();
+  jobject ctx_obj = cubeb_jni_get_context_instance();
+
+  if (!javaVM || !ctx_obj) {
+    return nullptr;
+  }
+
+  JNIEnv * jni_env = cubeb_jni_get_env_for_thread(javaVM);
+  assert(jni_env);
+
+  cubeb_jni * cubeb_jni_ptr = new cubeb_jni;
+  assert(cubeb_jni_ptr);
+
+  cubeb_jni_ptr->s_java_vm = javaVM;
+
+  // 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<jobject>(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<jclass>(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);
+
+  return cubeb_jni_ptr;
+}
+
+extern "C"
+int cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr)
+{
+  assert(cubeb_jni_ptr);
+  JNIEnv * jni_env = cubeb_jni_get_env_for_thread(cubeb_jni_ptr->s_java_vm);
+  return jni_env->CallIntMethod(cubeb_jni_ptr->s_audio_manager_obj, cubeb_jni_ptr->s_get_output_latency_id, AUDIO_STREAM_TYPE_MUSIC); //param: AudioManager.STREAM_MUSIC
+}
+
+extern "C"
+void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr)
+{
+  assert(cubeb_jni_ptr);
+
+  JNIEnv * jni_env = cubeb_jni_get_env_for_thread(cubeb_jni_ptr->s_java_vm);
+  assert(jni_env);
+
+  jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_obj);
+  jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_class);
+
+  delete cubeb_jni_ptr;
+}
new file mode 100644
--- /dev/null
+++ b/media/libcubeb/src/cubeb-jni.h
@@ -0,0 +1,10 @@
+#ifndef _CUBEB_JNI_H_
+#define _CUBEB_JNI_H_
+
+typedef struct cubeb_jni cubeb_jni;
+
+cubeb_jni * cubeb_jni_init();
+int cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr);
+void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr);
+
+#endif // _CUBEB_JNI_H_
--- a/media/libcubeb/src/cubeb_opensl.c
+++ b/media/libcubeb/src/cubeb_opensl.c
@@ -21,16 +21,17 @@
 #include <android/log.h>
 #include <android/api-level.h>
 #endif
 #include "cubeb/cubeb.h"
 #include "cubeb-internal.h"
 #include "cubeb_resampler.h"
 #include "cubeb-sles.h"
 #include "cubeb_array_queue.h"
+#include "cubeb-jni.h"
 
 #if defined(__ANDROID__)
 #ifdef LOG
 #undef LOG
 #endif
 //#define LOGGING_ENABLED
 #ifdef LOGGING_ENABLED
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args)
@@ -63,34 +64,32 @@
 #define DEFAULT_SAMPLE_RATE 48000
 #define DEFAULT_NUM_OF_FRAMES 480
 
 static struct cubeb_ops const opensl_ops;
 
 struct cubeb {
   struct cubeb_ops const * ops;
   void * lib;
-  void * libmedia;
-  int32_t (* get_output_latency)(uint32_t * latency, int stream_type);
   SLInterfaceID SL_IID_BUFFERQUEUE;
   SLInterfaceID SL_IID_PLAY;
 #if defined(__ANDROID__)
   SLInterfaceID SL_IID_ANDROIDCONFIGURATION;
   SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
 #endif
   SLInterfaceID SL_IID_VOLUME;
   SLInterfaceID SL_IID_RECORD;
   SLObjectItf engObj;
   SLEngineItf eng;
   SLObjectItf outmixObj;
+  cubeb_jni * jni_obj;
 };
 
 #define NELEMS(A) (sizeof(A) / sizeof A[0])
 #define NBUFS 4
-#define AUDIO_STREAM_TYPE_MUSIC 3
 
 struct cubeb_stream {
   /* Note: Must match cubeb_stream layout in cubeb.c. */
   cubeb * context;
   void * user_ptr;
   /**/
   pthread_mutex_t mutex;
   SLObjectItf playerObj;
@@ -663,40 +662,21 @@ opensl_init(cubeb ** context, char const
   *context = NULL;
 
   ctx = calloc(1, sizeof(*ctx));
   assert(ctx);
 
   ctx->ops = &opensl_ops;
 
   ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY);
-  ctx->libmedia = dlopen("libmedia.so", RTLD_LAZY);
-  if (!ctx->lib || !ctx->libmedia) {
+  if (!ctx->lib) {
     free(ctx);
     return CUBEB_ERROR;
   }
 
-  /* Get the latency, in ms, from AudioFlinger */
-  /* status_t AudioSystem::getOutputLatency(uint32_t* latency,
-   *                                        audio_stream_type_t streamType) */
-  /* First, try the most recent signature. */
-  ctx->get_output_latency =
-    dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
-  if (!ctx->get_output_latency) {
-    /* in case of failure, try the legacy version. */
-    /* status_t AudioSystem::getOutputLatency(uint32_t* latency,
-     *                                        int streamType) */
-    ctx->get_output_latency =
-      dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
-    if (!ctx->get_output_latency) {
-      opensl_destroy(ctx);
-      return CUBEB_ERROR;
-    }
-  }
-
   typedef SLresult (*slCreateEngine_t)(SLObjectItf *,
                                        SLuint32,
                                        const SLEngineOption *,
                                        SLuint32,
                                        const SLInterfaceID *,
                                        const SLboolean *);
   slCreateEngine_t f_slCreateEngine =
     (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine");
@@ -756,16 +736,21 @@ opensl_init(cubeb ** context, char const
   }
 
   res = (*ctx->outmixObj)->Realize(ctx->outmixObj, SL_BOOLEAN_FALSE);
   if (res != SL_RESULT_SUCCESS) {
     opensl_destroy(ctx);
     return CUBEB_ERROR;
   }
 
+  ctx->jni_obj = cubeb_jni_init();
+  if (!ctx->jni_obj) {
+    LOG("Warning: jni is not initialized, cubeb_stream_get_position() is not supported");
+  }
+
   *context = ctx;
 
   LOG("Cubeb init (%p) success", ctx);
   return CUBEB_OK;
 }
 
 static char const *
 opensl_get_backend_id(cubeb * ctx)
@@ -787,17 +772,18 @@ opensl_get_max_channel_count(cubeb * ctx
 static void
 opensl_destroy(cubeb * ctx)
 {
   if (ctx->outmixObj)
     (*ctx->outmixObj)->Destroy(ctx->outmixObj);
   if (ctx->engObj)
     cubeb_destroy_sles_engine(&ctx->engObj);
   dlclose(ctx->lib);
-  dlclose(ctx->libmedia);
+  if (ctx->jni_obj)
+    cubeb_jni_destroy(ctx->jni_obj);
   free(ctx);
 }
 
 static void opensl_stream_destroy(cubeb_stream * stm);
 
 static int
 opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params)
 {
@@ -874,18 +860,18 @@ opensl_configure_capture(cubeb_stream * 
                                                            lSoundRecorderReqs);
   // Sample rate not supported. Try again with default sample rate!
   if (res == SL_RESULT_CONTENT_UNSUPPORTED) {
     if (stm->output_enabled && stm->output_configured_rate != 0) {
       // Set the same with the player. Since there is no
       // api for input device this is a safe choice.
       stm->input_device_rate = stm->output_configured_rate;
     } else  {
-      // The output preferred rate is used for input only scenario. The
-      // default rate expected to supported from all android devices.
+      // The output preferred rate is used for an input only scenario.
+      // The default rate expected to be supported from all android devices.
       stm->input_device_rate = DEFAULT_SAMPLE_RATE;
     }
     lDataFormat.samplesPerSec = stm->input_device_rate * 1000;
     res = (*stm->context->eng)->CreateAudioRecorder(stm->context->eng,
                                                     &stm->recorderObj,
                                                     &lDataSource,
                                                     &lDataSink,
                                                     lSoundRecorderIIDCount,
@@ -1038,17 +1024,17 @@ opensl_configure_playback(cubeb_stream *
                                                   &sink,
                                                   NELEMS(ids),
                                                   ids,
                                                   req);
   }
 
   // Sample rate not supported? Try again with primary sample rate!
   if (res == SL_RESULT_CONTENT_UNSUPPORTED &&
-        preferred_sampling_rate != DEFAULT_SAMPLE_RATE) {
+      preferred_sampling_rate != DEFAULT_SAMPLE_RATE) {
     preferred_sampling_rate = DEFAULT_SAMPLE_RATE;
     format.samplesPerSec = preferred_sampling_rate * 1000;
     res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng,
                                                   &stm->playerObj,
                                                   &source,
                                                   &sink,
                                                   NELEMS(ids),
                                                   ids,
@@ -1458,42 +1444,39 @@ opensl_stream_destroy(cubeb_stream * stm
   LOG("Cubeb stream (%p) destroyed", stm);
   free(stm);
 }
 
 static int
 opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
 {
   SLmillisecond msec;
-  uint64_t samplerate;
+  uint32_t compensation_msec = 0;
   SLresult res;
-  int r;
-  uint32_t mixer_latency;
-  uint32_t compensation_msec = 0;
+
+  if (!stm->context->jni_obj) {
+    return CUBEB_ERROR_NOT_SUPPORTED;
+  }
 
   res = (*stm->play)->GetPosition(stm->play, &msec);
   if (res != SL_RESULT_SUCCESS)
     return CUBEB_ERROR;
 
   struct timespec t;
   clock_gettime(CLOCK_MONOTONIC, &t);
   if(stm->lastPosition == msec) {
     compensation_msec =
       (t.tv_sec*1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) / 1000000;
   } else {
     stm->lastPositionTimeStamp = t.tv_sec*1000000000LL + t.tv_nsec;
     stm->lastPosition = msec;
   }
 
-  samplerate = stm->user_output_rate;
-
-  r = stm->context->get_output_latency(&mixer_latency, AUDIO_STREAM_TYPE_MUSIC);
-  if (r) {
-    return CUBEB_ERROR;
-  }
+  uint64_t samplerate = stm->user_output_rate;
+  uint32_t mixer_latency = cubeb_get_output_latency_from_jni(stm->context->jni_obj);
 
   pthread_mutex_lock(&stm->mutex);
   int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate / stm->output_configured_rate;
   pthread_mutex_unlock(&stm->mutex);
   assert(maximum_position >= 0);
 
   if (msec > mixer_latency) {
     int64_t unadjusted_position;
--- a/media/libcubeb/src/moz.build
+++ b/media/libcubeb/src/moz.build
@@ -72,16 +72,17 @@ if CONFIG['OS_TARGET'] == 'WINNT':
       "avrt",
     ]
     if CONFIG['CC_TYPE'] in ('msvc', 'clang-cl'):
         CXXFLAGS += ['-wd4005'] # C4005: '_USE_MATH_DEFINES' : macro redefinition
 
 if CONFIG['OS_TARGET'] == 'Android':
     SOURCES += ['cubeb_opensl.c']
     SOURCES += ['cubeb_resampler.cpp']
+    SOURCES += ['cubeb-jni.cpp']
     DEFINES['USE_OPENSL'] = True
     SOURCES += [
         'cubeb_audiotrack.c',
     ]
     DEFINES['USE_AUDIOTRACK'] = True
 
 FINAL_LIBRARY = 'gkmedias'