| /* |
| * Copyright (C) 2012, Collabora Ltd. |
| * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk> |
| * Copyright (C) 2013, Fluendo S.A. |
| * Author: Andoni Morales <amorales@fluendo.com> |
| * Copyright (C) 2014, Sebastian Dröge <sebastian@centricular.com> |
| * Copyright (C) 2014, Collabora Ltd. |
| * Author: Matthieu Bouron <matthieu.bouron@collabora.com> |
| * Copyright (C) 2015, Sebastian Dröge <sebastian@centricular.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation |
| * version 2.1 of the License. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <gst/gst.h> |
| #include <pthread.h> |
| #include <gmodule.h> |
| |
| #include "gstjniutils.h" |
| |
| static GModule *java_module; |
| static jint (*get_created_java_vms) (JavaVM ** vmBuf, jsize bufLen, |
| jsize * nVMs); |
| static jint (*create_java_vm) (JavaVM ** p_vm, JNIEnv ** p_env, void *vm_args); |
| static JavaVM *java_vm; |
| static gboolean started_java_vm = FALSE; |
| static pthread_key_t current_jni_env; |
| static jobject (*get_class_loader) (void); |
| |
| static struct |
| { |
| jclass klass; |
| jmethodID get_limit, get_position; |
| jmethodID set_limit, set_position; |
| jmethodID clear; |
| } java_nio_buffer; |
| |
| jclass |
| gst_amc_jni_get_class (JNIEnv * env, GError ** err, const gchar * name) |
| { |
| jclass tmp, ret = NULL; |
| |
| GST_DEBUG ("Retrieving Java class %s", name); |
| |
| tmp = (*env)->FindClass (env, name); |
| if ((*env)->ExceptionCheck (env) || !tmp) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, "Failed to find class %s", name); |
| goto done; |
| } |
| |
| ret = (*env)->NewGlobalRef (env, tmp); |
| if (!ret) { |
| GST_ERROR ("Failed to get %s class global reference", name); |
| } |
| |
| done: |
| if (tmp) |
| (*env)->DeleteLocalRef (env, tmp); |
| tmp = NULL; |
| |
| return ret; |
| } |
| |
| jmethodID |
| gst_amc_jni_get_method_id (JNIEnv * env, GError ** err, jclass klass, |
| const gchar * name, const gchar * signature) |
| { |
| jmethodID ret; |
| |
| ret = (*env)->GetMethodID (env, klass, name, signature); |
| if ((*env)->ExceptionCheck (env) || !ret) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, "Failed to get method ID %s (%s)", name, |
| signature); |
| } |
| return ret; |
| } |
| |
| jmethodID |
| gst_amc_jni_get_static_method_id (JNIEnv * env, GError ** err, jclass klass, |
| const gchar * name, const gchar * signature) |
| { |
| jmethodID ret; |
| |
| ret = (*env)->GetStaticMethodID (env, klass, name, signature); |
| if ((*env)->ExceptionCheck (env) || !ret) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, "Failed to get static method ID %s (%s)", |
| name, signature); |
| } |
| return ret; |
| } |
| |
| jfieldID |
| gst_amc_jni_get_field_id (JNIEnv * env, GError ** err, jclass klass, |
| const gchar * name, const gchar * type) |
| { |
| jfieldID ret; |
| |
| ret = (*env)->GetFieldID (env, klass, name, type); |
| if ((*env)->ExceptionCheck (env) || !ret) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, "Failed to get field ID %s (%s)", name, type); |
| } |
| return ret; |
| } |
| |
| jfieldID |
| gst_amc_jni_get_static_field_id (JNIEnv * env, GError ** err, jclass klass, |
| const gchar * name, const gchar * type) |
| { |
| jfieldID ret; |
| |
| ret = (*env)->GetStaticFieldID (env, klass, name, type); |
| if ((*env)->ExceptionCheck (env) || !ret) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, "Failed to get static field ID %s (%s)", name, |
| type); |
| } |
| return ret; |
| } |
| |
| jobject |
| gst_amc_jni_new_object (JNIEnv * env, GError ** err, gboolean global, |
| jclass klass, jmethodID constructor, ...) |
| { |
| jobject tmp; |
| va_list args; |
| |
| va_start (args, constructor); |
| tmp = (*env)->NewObjectV (env, klass, constructor, args); |
| va_end (args); |
| |
| if ((*env)->ExceptionCheck (env) || !tmp) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, "Failed to create object"); |
| return NULL; |
| } |
| |
| if (global) |
| return gst_amc_jni_object_make_global (env, tmp); |
| else |
| return tmp; |
| } |
| |
| jobject |
| gst_amc_jni_new_object_from_static (JNIEnv * env, GError ** err, |
| gboolean global, jclass klass, jmethodID method, ...) |
| { |
| jobject tmp; |
| va_list args; |
| |
| va_start (args, method); |
| tmp = (*env)->CallStaticObjectMethodV (env, klass, method, args); |
| va_end (args); |
| |
| if ((*env)->ExceptionCheck (env) || !tmp) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, "Failed to create object"); |
| return NULL; |
| } |
| |
| if (global) |
| return gst_amc_jni_object_make_global (env, tmp); |
| else |
| return tmp; |
| } |
| |
| jobject |
| gst_amc_jni_object_make_global (JNIEnv * env, jobject object) |
| { |
| jobject ret; |
| |
| ret = (*env)->NewGlobalRef (env, object); |
| if (!ret) { |
| GST_ERROR ("Failed to create global reference"); |
| } |
| gst_amc_jni_object_local_unref (env, object); |
| |
| return ret; |
| } |
| |
| jobject |
| gst_amc_jni_object_ref (JNIEnv * env, jobject object) |
| { |
| jobject ret; |
| |
| ret = (*env)->NewGlobalRef (env, object); |
| if (!ret) { |
| GST_ERROR ("Failed to create global reference"); |
| } |
| return ret; |
| } |
| |
| void |
| gst_amc_jni_object_unref (JNIEnv * env, jobject object) |
| { |
| g_return_if_fail (object != NULL); |
| |
| (*env)->DeleteGlobalRef (env, object); |
| } |
| |
| void |
| gst_amc_jni_object_local_unref (JNIEnv * env, jobject object) |
| { |
| g_return_if_fail (object != NULL); |
| |
| (*env)->DeleteLocalRef (env, object); |
| } |
| |
| jstring |
| gst_amc_jni_string_from_gchar (JNIEnv * env, GError ** err, |
| gboolean global, const gchar * string) |
| { |
| jstring tmp; |
| |
| tmp = (*env)->NewStringUTF (env, string); |
| if ((*env)->ExceptionCheck (env)) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, "Failed to call Java method"); |
| tmp = NULL; |
| } |
| |
| if (global) |
| return gst_amc_jni_object_make_global (env, tmp); |
| else |
| return tmp; |
| } |
| |
| gchar * |
| gst_amc_jni_string_to_gchar (JNIEnv * env, jstring string, gboolean release) |
| { |
| const gchar *s = NULL; |
| gchar *ret = NULL; |
| |
| s = (*env)->GetStringUTFChars (env, string, NULL); |
| if (!s) { |
| GST_ERROR ("Failed to convert string to UTF8"); |
| goto done; |
| } |
| |
| ret = g_strdup (s); |
| (*env)->ReleaseStringUTFChars (env, string, s); |
| |
| done: |
| if (release) { |
| (*env)->DeleteLocalRef (env, string); |
| } |
| return ret; |
| } |
| |
| /* getExceptionSummary() and getStackTrace() taken from Android's |
| * platform/libnativehelper/JNIHelp.cpp |
| * Modified to work with normal C strings and without C++. |
| * |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * Returns a human-readable summary of an exception object. The buffer will |
| * be populated with the "binary" class name and, if present, the |
| * exception message. |
| */ |
| static gchar * |
| getExceptionSummary (JNIEnv * env, jthrowable exception) |
| { |
| GString *gs = g_string_new (""); |
| jclass exceptionClass = NULL, classClass = NULL; |
| jmethodID classGetNameMethod, getMessage; |
| jstring classNameStr = NULL, messageStr = NULL; |
| const char *classNameChars, *messageChars; |
| |
| /* get the name of the exception's class */ |
| exceptionClass = (*env)->GetObjectClass (env, exception); |
| classClass = (*env)->GetObjectClass (env, exceptionClass); |
| classGetNameMethod = |
| (*env)->GetMethodID (env, classClass, "getName", "()Ljava/lang/String;"); |
| |
| classNameStr = |
| (jstring) (*env)->CallObjectMethod (env, exceptionClass, |
| classGetNameMethod); |
| |
| if (classNameStr == NULL) { |
| if ((*env)->ExceptionCheck (env)) |
| (*env)->ExceptionClear (env); |
| g_string_append (gs, "<error getting class name>"); |
| goto done; |
| } |
| |
| classNameChars = (*env)->GetStringUTFChars (env, classNameStr, NULL); |
| if (classNameChars == NULL) { |
| if ((*env)->ExceptionCheck (env)) |
| (*env)->ExceptionClear (env); |
| g_string_append (gs, "<error getting class name UTF-8>"); |
| goto done; |
| } |
| |
| g_string_append (gs, classNameChars); |
| |
| (*env)->ReleaseStringUTFChars (env, classNameStr, classNameChars); |
| |
| /* if the exception has a detail message, get that */ |
| getMessage = |
| (*env)->GetMethodID (env, exceptionClass, "getMessage", |
| "()Ljava/lang/String;"); |
| messageStr = (jstring) (*env)->CallObjectMethod (env, exception, getMessage); |
| if (messageStr == NULL) { |
| if ((*env)->ExceptionCheck (env)) |
| (*env)->ExceptionClear (env); |
| goto done; |
| } |
| g_string_append (gs, ": "); |
| |
| messageChars = (*env)->GetStringUTFChars (env, messageStr, NULL); |
| if (messageChars != NULL) { |
| g_string_append (gs, messageChars); |
| (*env)->ReleaseStringUTFChars (env, messageStr, messageChars); |
| } else { |
| if ((*env)->ExceptionCheck (env)) |
| (*env)->ExceptionClear (env); |
| g_string_append (gs, "<error getting message>"); |
| } |
| |
| done: |
| if (exceptionClass) |
| (*env)->DeleteLocalRef (env, exceptionClass); |
| if (classClass) |
| (*env)->DeleteLocalRef (env, classClass); |
| if (classNameStr) |
| (*env)->DeleteLocalRef (env, classNameStr); |
| if (messageStr) |
| (*env)->DeleteLocalRef (env, messageStr); |
| |
| return g_string_free (gs, FALSE); |
| } |
| |
| /* |
| * Returns an exception (with stack trace) as a string. |
| */ |
| static gchar * |
| getStackTrace (JNIEnv * env, jthrowable exception) |
| { |
| GString *gs = g_string_new (""); |
| jclass stringWriterClass = NULL, printWriterClass = NULL; |
| jclass exceptionClass = NULL; |
| jmethodID stringWriterCtor, stringWriterToStringMethod; |
| jmethodID printWriterCtor, printStackTraceMethod; |
| jobject stringWriter = NULL, printWriter = NULL; |
| jstring messageStr = NULL; |
| const char *utfChars; |
| |
| stringWriterClass = (*env)->FindClass (env, "java/io/StringWriter"); |
| |
| if (stringWriterClass == NULL) { |
| g_string_append (gs, "<error getting java.io.StringWriter class>"); |
| goto done; |
| } |
| |
| stringWriterCtor = |
| (*env)->GetMethodID (env, stringWriterClass, "<init>", "()V"); |
| stringWriterToStringMethod = |
| (*env)->GetMethodID (env, stringWriterClass, "toString", |
| "()Ljava/lang/String;"); |
| |
| printWriterClass = (*env)->FindClass (env, "java/io/PrintWriter"); |
| if (printWriterClass == NULL) { |
| g_string_append (gs, "<error getting java.io.PrintWriter class>"); |
| goto done; |
| } |
| |
| printWriterCtor = |
| (*env)->GetMethodID (env, printWriterClass, "<init>", |
| "(Ljava/io/Writer;)V"); |
| stringWriter = (*env)->NewObject (env, stringWriterClass, stringWriterCtor); |
| if (stringWriter == NULL) { |
| if ((*env)->ExceptionCheck (env)) |
| (*env)->ExceptionClear (env); |
| g_string_append (gs, "<error creating new StringWriter instance>"); |
| goto done; |
| } |
| |
| printWriter = |
| (*env)->NewObject (env, printWriterClass, printWriterCtor, stringWriter); |
| if (printWriter == NULL) { |
| if ((*env)->ExceptionCheck (env)) |
| (*env)->ExceptionClear (env); |
| g_string_append (gs, "<error creating new PrintWriter instance>"); |
| goto done; |
| } |
| |
| exceptionClass = (*env)->GetObjectClass (env, exception); |
| printStackTraceMethod = |
| (*env)->GetMethodID (env, exceptionClass, "printStackTrace", |
| "(Ljava/io/PrintWriter;)V"); |
| (*env)->CallVoidMethod (env, exception, printStackTraceMethod, printWriter); |
| if ((*env)->ExceptionCheck (env)) { |
| (*env)->ExceptionClear (env); |
| g_string_append (gs, "<exception while printing stack trace>"); |
| goto done; |
| } |
| |
| messageStr = (jstring) (*env)->CallObjectMethod (env, stringWriter, |
| stringWriterToStringMethod); |
| if (messageStr == NULL) { |
| if ((*env)->ExceptionCheck (env)) |
| (*env)->ExceptionClear (env); |
| g_string_append (gs, "<failed to call StringWriter.toString()>"); |
| goto done; |
| } |
| |
| utfChars = (*env)->GetStringUTFChars (env, messageStr, NULL); |
| if (utfChars == NULL) { |
| if ((*env)->ExceptionCheck (env)) |
| (*env)->ExceptionClear (env); |
| g_string_append (gs, "<failed to get UTF chars for message>"); |
| goto done; |
| } |
| |
| g_string_append (gs, utfChars); |
| |
| (*env)->ReleaseStringUTFChars (env, messageStr, utfChars); |
| |
| done: |
| if (stringWriterClass) |
| (*env)->DeleteLocalRef (env, stringWriterClass); |
| if (printWriterClass) |
| (*env)->DeleteLocalRef (env, printWriterClass); |
| if (exceptionClass) |
| (*env)->DeleteLocalRef (env, exceptionClass); |
| if (stringWriter) |
| (*env)->DeleteLocalRef (env, stringWriter); |
| if (printWriter) |
| (*env)->DeleteLocalRef (env, printWriter); |
| if (messageStr) |
| (*env)->DeleteLocalRef (env, messageStr); |
| |
| return g_string_free (gs, FALSE); |
| } |
| |
| static JNIEnv * |
| gst_amc_jni_attach_current_thread (void) |
| { |
| JNIEnv *env; |
| JavaVMAttachArgs args; |
| gint ret; |
| |
| GST_DEBUG ("Attaching thread %p", g_thread_self ()); |
| args.version = JNI_VERSION_1_6; |
| args.name = NULL; |
| args.group = NULL; |
| |
| if ((ret = (*java_vm)->AttachCurrentThread (java_vm, &env, &args)) != JNI_OK) { |
| GST_ERROR ("Failed to attach current thread: %d", ret); |
| return NULL; |
| } |
| |
| return env; |
| } |
| |
| static void |
| gst_amc_jni_detach_current_thread (void *env) |
| { |
| gint ret; |
| |
| GST_DEBUG ("Detaching thread %p", g_thread_self ()); |
| if ((ret = (*java_vm)->DetachCurrentThread (java_vm)) != JNI_OK) { |
| GST_DEBUG ("Failed to detach current thread: %d", ret); |
| } |
| } |
| |
| static gboolean |
| check_nativehelper (void) |
| { |
| GModule *module; |
| void **jni_invocation = NULL; |
| gboolean ret = FALSE; |
| |
| module = g_module_open (NULL, G_MODULE_BIND_LOCAL); |
| if (!module) |
| return ret; |
| |
| /* Check if libnativehelper is loaded in the process and if |
| * it has these awful wrappers for JNI_CreateJavaVM and |
| * JNI_GetCreatedJavaVMs that crash the app if you don't |
| * create a JniInvocation instance first. If it isn't we |
| * just fail here and don't initialize anything. |
| * See this code for reference: |
| * https://android.googlesource.com/platform/libnativehelper/+/master/JniInvocation.cpp |
| */ |
| if (!g_module_symbol (module, "_ZN13JniInvocation15jni_invocation_E", |
| (gpointer *) & jni_invocation)) { |
| ret = TRUE; |
| } else { |
| ret = (jni_invocation != NULL && *jni_invocation != NULL); |
| } |
| |
| g_module_close (module); |
| |
| return ret; |
| } |
| |
| static gboolean |
| load_java_module (const gchar * name) |
| { |
| java_module = g_module_open (name, G_MODULE_BIND_LOCAL); |
| if (!java_module) |
| goto load_failed; |
| |
| if (!g_module_symbol (java_module, "JNI_CreateJavaVM", |
| (gpointer *) & create_java_vm)) { |
| GST_ERROR ("Could not find 'JNI_CreateJavaVM' in '%s': %s", |
| GST_STR_NULL (name), g_module_error ()); |
| create_java_vm = NULL; |
| } |
| |
| if (!g_module_symbol (java_module, "JNI_GetCreatedJavaVMs", |
| (gpointer *) & get_created_java_vms)) |
| goto symbol_error; |
| |
| return TRUE; |
| |
| load_failed: |
| { |
| GST_ERROR ("Failed to load Java module '%s': %s", GST_STR_NULL (name), |
| g_module_error ()); |
| return FALSE; |
| } |
| symbol_error: |
| { |
| GST_ERROR ("Failed to locate required JNI symbols in '%s': %s", |
| GST_STR_NULL (name), g_module_error ()); |
| g_module_close (java_module); |
| java_module = NULL; |
| return FALSE; |
| } |
| } |
| |
| static gboolean |
| check_application_class_loader (void) |
| { |
| gboolean ret = TRUE; |
| GModule *module = NULL; |
| |
| module = g_module_open (NULL, G_MODULE_BIND_LOCAL); |
| if (!module) { |
| return FALSE; |
| } |
| if (!g_module_symbol (module, "gst_android_get_application_class_loader", |
| (gpointer *) & get_class_loader)) { |
| ret = FALSE; |
| } |
| |
| g_module_close (module); |
| |
| return ret; |
| } |
| |
| static gboolean |
| initialize_classes (void) |
| { |
| JNIEnv *env; |
| GError *err = NULL; |
| |
| env = gst_amc_jni_get_env (); |
| |
| java_nio_buffer.klass = gst_amc_jni_get_class (env, &err, "java/nio/Buffer"); |
| if (!java_nio_buffer.klass) { |
| GST_ERROR ("Failed to get java.nio.Buffer class: %s", err->message); |
| g_clear_error (&err); |
| return FALSE; |
| } |
| |
| java_nio_buffer.get_limit = |
| gst_amc_jni_get_method_id (env, &err, java_nio_buffer.klass, "limit", |
| "()I"); |
| if (!java_nio_buffer.get_limit) { |
| GST_ERROR ("Failed to get java.nio.Buffer limit(): %s", err->message); |
| g_clear_error (&err); |
| return FALSE; |
| } |
| |
| java_nio_buffer.get_position = |
| gst_amc_jni_get_method_id (env, &err, java_nio_buffer.klass, "position", |
| "()I"); |
| if (!java_nio_buffer.get_position) { |
| GST_ERROR ("Failed to get java.nio.Buffer position(): %s", err->message); |
| g_clear_error (&err); |
| return FALSE; |
| } |
| |
| java_nio_buffer.set_limit = |
| gst_amc_jni_get_method_id (env, &err, java_nio_buffer.klass, "limit", |
| "(I)Ljava/nio/Buffer;"); |
| if (!java_nio_buffer.set_limit) { |
| GST_ERROR ("Failed to get java.nio.Buffer limit(): %s", err->message); |
| g_clear_error (&err); |
| return FALSE; |
| } |
| |
| java_nio_buffer.set_position = |
| gst_amc_jni_get_method_id (env, &err, java_nio_buffer.klass, "position", |
| "(I)Ljava/nio/Buffer;"); |
| if (!java_nio_buffer.set_position) { |
| GST_ERROR ("Failed to get java.nio.Buffer position(): %s", err->message); |
| g_clear_error (&err); |
| return FALSE; |
| } |
| |
| java_nio_buffer.clear = |
| gst_amc_jni_get_method_id (env, &err, java_nio_buffer.klass, "clear", |
| "()Ljava/nio/Buffer;"); |
| if (!java_nio_buffer.clear) { |
| GST_ERROR ("Failed to get java.nio.Buffer clear(): %s", err->message); |
| g_clear_error (&err); |
| return FALSE; |
| } |
| |
| if (!check_application_class_loader ()) { |
| GST_ERROR ("Could not find application class loader provider"); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_amc_jni_initialize_java_vm (void) |
| { |
| jsize n_vms; |
| gint ret; |
| |
| if (java_vm) { |
| GST_DEBUG ("Java VM already provided by the application"); |
| return initialize_classes (); |
| } |
| |
| /* Returns TRUE if we can safely |
| * a) get the current VMs and |
| * b) start a VM if none is started yet |
| * |
| * FIXME: On Android >= 4.4 we won't be able to safely start a |
| * VM on our own without using private C++ API! |
| */ |
| if (!check_nativehelper ()) { |
| GST_ERROR ("Can't safely check for VMs or start a VM"); |
| return FALSE; |
| } |
| |
| if (!load_java_module (NULL)) { |
| if (!load_java_module ("libdvm")) |
| return FALSE; |
| } |
| |
| n_vms = 0; |
| if ((ret = get_created_java_vms (&java_vm, 1, &n_vms)) != JNI_OK) |
| goto get_created_failed; |
| |
| if (n_vms > 0) { |
| GST_DEBUG ("Successfully got existing Java VM %p", java_vm); |
| } else if (create_java_vm) { |
| JNIEnv *env; |
| JavaVMInitArgs vm_args; |
| JavaVMOption options[4]; |
| |
| GST_DEBUG ("Found no existing Java VM, trying to start one"); |
| |
| options[0].optionString = "-verbose:jni"; |
| options[1].optionString = "-verbose:gc"; |
| options[2].optionString = "-Xcheck:jni"; |
| options[3].optionString = "-Xdebug"; |
| |
| vm_args.version = JNI_VERSION_1_4; |
| vm_args.options = options; |
| vm_args.nOptions = 4; |
| vm_args.ignoreUnrecognized = JNI_TRUE; |
| if ((ret = create_java_vm (&java_vm, &env, &vm_args)) != JNI_OK) |
| goto create_failed; |
| GST_DEBUG ("Successfully created Java VM %p", java_vm); |
| |
| started_java_vm = TRUE; |
| } else { |
| GST_ERROR ("JNI_CreateJavaVM not available"); |
| java_vm = NULL; |
| } |
| |
| if (java_vm == NULL) |
| return FALSE; |
| |
| return initialize_classes (); |
| |
| get_created_failed: |
| { |
| GST_ERROR ("Failed to get already created VMs: %d", ret); |
| g_module_close (java_module); |
| java_module = NULL; |
| return FALSE; |
| } |
| create_failed: |
| { |
| GST_ERROR ("Failed to create a Java VM: %d", ret); |
| g_module_close (java_module); |
| java_module = NULL; |
| return FALSE; |
| } |
| } |
| |
| static void |
| gst_amc_jni_set_error_string (JNIEnv * env, GError ** err, GQuark domain, |
| gint code, const gchar * message) |
| { |
| jthrowable exception; |
| |
| if (!err) { |
| if ((*env)->ExceptionCheck (env)) |
| (*env)->ExceptionClear (env); |
| return; |
| } |
| |
| if ((*env)->ExceptionCheck (env)) { |
| if ((exception = (*env)->ExceptionOccurred (env))) { |
| gchar *exception_description, *exception_stacktrace; |
| |
| /* Clear exception so that we can call Java methods again */ |
| (*env)->ExceptionClear (env); |
| |
| exception_description = getExceptionSummary (env, exception); |
| exception_stacktrace = getStackTrace (env, exception); |
| g_set_error (err, domain, code, "%s: %s\n%s", message, |
| exception_description, exception_stacktrace); |
| g_free (exception_description); |
| g_free (exception_stacktrace); |
| |
| (*env)->DeleteLocalRef (env, exception); |
| } else { |
| (*env)->ExceptionClear (env); |
| g_set_error (err, domain, code, "%s", message); |
| } |
| } else { |
| g_set_error (err, domain, code, "%s", message); |
| } |
| } |
| |
| G_GNUC_PRINTF (5, 6) |
| void gst_amc_jni_set_error (JNIEnv * env, GError ** err, GQuark domain, |
| gint code, const gchar * format, ...) |
| { |
| gchar *message; |
| va_list var_args; |
| |
| va_start (var_args, format); |
| message = g_strdup_vprintf (format, var_args); |
| va_end (var_args); |
| |
| gst_amc_jni_set_error_string (env, err, domain, code, message); |
| |
| g_free (message); |
| } |
| |
| static gpointer |
| gst_amc_jni_initialize_internal (gpointer data) |
| { |
| pthread_key_create (¤t_jni_env, gst_amc_jni_detach_current_thread); |
| return gst_amc_jni_initialize_java_vm ()? GINT_TO_POINTER (1) : NULL; |
| } |
| |
| /* Allow the application to set the Java VM */ |
| void |
| gst_amc_jni_set_java_vm (JavaVM * vm) |
| { |
| GST_DEBUG ("Application provides Java VM %p", vm); |
| java_vm = vm; |
| } |
| |
| gboolean |
| gst_amc_jni_initialize (void) |
| { |
| GOnce once = G_ONCE_INIT; |
| |
| g_once (&once, gst_amc_jni_initialize_internal, NULL); |
| return once.retval != NULL; |
| } |
| |
| JNIEnv * |
| gst_amc_jni_get_env (void) |
| { |
| JNIEnv *env; |
| |
| if ((env = pthread_getspecific (current_jni_env)) == NULL) { |
| env = gst_amc_jni_attach_current_thread (); |
| pthread_setspecific (current_jni_env, env); |
| } |
| |
| return env; |
| } |
| |
| gboolean |
| gst_amc_jni_is_vm_started (void) |
| { |
| return started_java_vm; |
| } |
| |
| jclass |
| gst_amc_jni_get_application_class (JNIEnv * env, const gchar * name, |
| GError ** err) |
| { |
| jclass tmp = NULL; |
| jclass class = NULL; |
| jstring name_jstr = NULL; |
| |
| jobject class_loader = NULL; |
| jclass class_loader_cls = NULL; |
| jmethodID load_class_id = 0; |
| |
| |
| class_loader = get_class_loader (); |
| if (!class_loader) { |
| g_set_error (err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, |
| "Could not retreive application class loader"); |
| goto done; |
| } |
| |
| class_loader_cls = (*env)->GetObjectClass (env, class_loader); |
| if (!class_loader_cls) { |
| g_set_error (err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, |
| "Could not retreive application class loader java class"); |
| goto done; |
| } |
| |
| load_class_id = |
| gst_amc_jni_get_method_id (env, err, class_loader_cls, "loadClass", |
| "(Ljava/lang/String;)Ljava/lang/Class;"); |
| if (!class_loader_cls) { |
| goto done; |
| } |
| |
| name_jstr = gst_amc_jni_string_from_gchar (env, err, FALSE, name); |
| if (!name_jstr) { |
| goto done; |
| } |
| |
| if (gst_amc_jni_call_object_method (env, err, class_loader, |
| load_class_id, &tmp, name_jstr)) { |
| class = gst_amc_jni_object_make_global (env, tmp); |
| } |
| |
| done: |
| gst_amc_jni_object_local_unref (env, name_jstr); |
| gst_amc_jni_object_local_unref (env, class_loader_cls); |
| |
| return class; |
| } |
| |
| #define CALL_STATIC_TYPE_METHOD(_type, _name, _jname) \ |
| gboolean gst_amc_jni_call_static_##_name##_method (JNIEnv *env, GError ** err, jclass klass, jmethodID methodID, _type * value, ...) \ |
| { \ |
| gboolean ret = TRUE; \ |
| va_list args; \ |
| va_start(args, value); \ |
| *value = (*env)->CallStatic##_jname##MethodV(env, klass, methodID, args); \ |
| if ((*env)->ExceptionCheck (env)) { \ |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, \ |
| "Failed to call static Java method"); \ |
| ret = FALSE; \ |
| } \ |
| va_end(args); \ |
| return ret; \ |
| } |
| |
| CALL_STATIC_TYPE_METHOD (gboolean, boolean, Boolean); |
| CALL_STATIC_TYPE_METHOD (gint8, byte, Byte); |
| CALL_STATIC_TYPE_METHOD (gshort, short, Short); |
| CALL_STATIC_TYPE_METHOD (gint, int, Int); |
| CALL_STATIC_TYPE_METHOD (gchar, char, Char); |
| CALL_STATIC_TYPE_METHOD (gint64, long, Long); |
| CALL_STATIC_TYPE_METHOD (gfloat, float, Float); |
| CALL_STATIC_TYPE_METHOD (gdouble, double, Double); |
| CALL_STATIC_TYPE_METHOD (jobject, object, Object); |
| |
| gboolean |
| gst_amc_jni_call_static_void_method (JNIEnv * env, GError ** err, jclass klass, |
| jmethodID methodID, ...) |
| { |
| gboolean ret = TRUE; |
| va_list args; |
| va_start (args, methodID); |
| |
| (*env)->CallStaticVoidMethodV (env, klass, methodID, args); |
| if ((*env)->ExceptionCheck (env)) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, "Failed to call static Java method"); |
| ret = FALSE; |
| } |
| va_end (args); |
| return ret; |
| } |
| |
| #define CALL_TYPE_METHOD(_type, _name, _jname) \ |
| gboolean gst_amc_jni_call_##_name##_method (JNIEnv *env, GError ** err, jobject obj, jmethodID methodID, _type *value, ...) \ |
| { \ |
| gboolean ret = TRUE; \ |
| va_list args; \ |
| va_start(args, value); \ |
| *value = (*env)->Call##_jname##MethodV(env, obj, methodID, args); \ |
| if ((*env)->ExceptionCheck (env)) { \ |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, \ |
| "Failed to call Java method"); \ |
| ret = FALSE; \ |
| } \ |
| va_end(args); \ |
| return ret; \ |
| } |
| |
| CALL_TYPE_METHOD (gboolean, boolean, Boolean); |
| CALL_TYPE_METHOD (gint8, byte, Byte); |
| CALL_TYPE_METHOD (gshort, short, Short); |
| CALL_TYPE_METHOD (gint, int, Int); |
| CALL_TYPE_METHOD (gchar, char, Char); |
| CALL_TYPE_METHOD (gint64, long, Long); |
| CALL_TYPE_METHOD (gfloat, float, Float); |
| CALL_TYPE_METHOD (gdouble, double, Double); |
| CALL_TYPE_METHOD (jobject, object, Object); |
| |
| gboolean |
| gst_amc_jni_call_void_method (JNIEnv * env, GError ** err, jobject obj, |
| jmethodID methodID, ...) |
| { |
| gboolean ret = TRUE; |
| va_list args; |
| va_start (args, methodID); |
| |
| (*env)->CallVoidMethodV (env, obj, methodID, args); |
| if ((*env)->ExceptionCheck (env)) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, "Failed to call Java method"); |
| ret = FALSE; |
| } |
| va_end (args); |
| return ret; |
| } |
| |
| #define GET_TYPE_FIELD(_type, _name, _jname) \ |
| gboolean gst_amc_jni_get_##_name##_field (JNIEnv *env, GError ** err, jobject obj, jfieldID fieldID, _type *value) \ |
| { \ |
| gboolean ret = TRUE; \ |
| \ |
| *value = (*env)->Get##_jname##Field(env, obj, fieldID); \ |
| if ((*env)->ExceptionCheck (env)) { \ |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, \ |
| "Failed to get Java field"); \ |
| ret = FALSE; \ |
| } \ |
| return ret; \ |
| } |
| |
| GET_TYPE_FIELD (gboolean, boolean, Boolean); |
| GET_TYPE_FIELD (gint8, byte, Byte); |
| GET_TYPE_FIELD (gshort, short, Short); |
| GET_TYPE_FIELD (gint, int, Int); |
| GET_TYPE_FIELD (gchar, char, Char); |
| GET_TYPE_FIELD (gint64, long, Long); |
| GET_TYPE_FIELD (gfloat, float, Float); |
| GET_TYPE_FIELD (gdouble, double, Double); |
| GET_TYPE_FIELD (jobject, object, Object); |
| |
| #define GET_STATIC_TYPE_FIELD(_type, _name, _jname) \ |
| gboolean gst_amc_jni_get_static_##_name##_field (JNIEnv *env, GError ** err, jclass klass, jfieldID fieldID, _type *value) \ |
| { \ |
| gboolean ret = TRUE; \ |
| \ |
| *value = (*env)->GetStatic##_jname##Field(env, klass, fieldID); \ |
| if ((*env)->ExceptionCheck (env)) { \ |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, \ |
| "Failed to get static Java field"); \ |
| ret = FALSE; \ |
| } \ |
| return ret; \ |
| } |
| |
| GET_STATIC_TYPE_FIELD (gboolean, boolean, Boolean); |
| GET_STATIC_TYPE_FIELD (gint8, byte, Byte); |
| GET_STATIC_TYPE_FIELD (gshort, short, Short); |
| GET_STATIC_TYPE_FIELD (gint, int, Int); |
| GET_STATIC_TYPE_FIELD (gchar, char, Char); |
| GET_STATIC_TYPE_FIELD (gint64, long, Long); |
| GET_STATIC_TYPE_FIELD (gfloat, float, Float); |
| GET_STATIC_TYPE_FIELD (gdouble, double, Double); |
| GET_STATIC_TYPE_FIELD (jobject, object, Object); |
| |
| gboolean |
| gst_amc_jni_get_buffer_array (JNIEnv * env, GError ** err, jobject array, |
| GstAmcBuffer ** buffers, gsize * n_buffers) |
| { |
| jsize i; |
| |
| *n_buffers = (*env)->GetArrayLength (env, array); |
| *buffers = g_new0 (GstAmcBuffer, *n_buffers); |
| |
| for (i = 0; i < *n_buffers; i++) { |
| jobject buffer = NULL; |
| |
| buffer = (*env)->GetObjectArrayElement (env, array, i); |
| if ((*env)->ExceptionCheck (env)) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, "Failed to get buffer %d", i); |
| goto error; |
| } |
| |
| /* NULL buffers are not a problem and are happening when we configured |
| * a surface as input/output */ |
| if (!buffer) |
| continue; |
| |
| (*buffers)[i].object = gst_amc_jni_object_make_global (env, buffer); |
| if (!(*buffers)[i].object) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, |
| "Failed to create global buffer reference %d", i); |
| goto error; |
| } |
| |
| (*buffers)[i].data = |
| (*env)->GetDirectBufferAddress (env, (*buffers)[i].object); |
| if (!(*buffers)[i].data) { |
| gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, |
| GST_LIBRARY_ERROR_FAILED, "Failed to get buffer address %d", i); |
| goto error; |
| } |
| (*buffers)[i].size = |
| (*env)->GetDirectBufferCapacity (env, (*buffers)[i].object); |
| } |
| |
| return TRUE; |
| |
| error: |
| if (*buffers) |
| gst_amc_jni_free_buffer_array (env, *buffers, *n_buffers); |
| *buffers = NULL; |
| *n_buffers = 0; |
| return FALSE; |
| } |
| |
| void |
| gst_amc_jni_free_buffer_array (JNIEnv * env, GstAmcBuffer * buffers, |
| gsize n_buffers) |
| { |
| jsize i; |
| |
| g_return_if_fail (buffers != NULL); |
| |
| for (i = 0; i < n_buffers; i++) { |
| if (buffers[i].object) |
| gst_amc_jni_object_unref (env, buffers[i].object); |
| } |
| g_free (buffers); |
| } |
| |
| void |
| gst_amc_buffer_free (GstAmcBuffer * buffer) |
| { |
| JNIEnv *env; |
| |
| g_return_if_fail (buffer != NULL); |
| |
| env = gst_amc_jni_get_env (); |
| |
| if (buffer->object) |
| gst_amc_jni_object_unref (env, buffer->object); |
| g_free (buffer); |
| } |
| |
| GstAmcBuffer * |
| gst_amc_buffer_copy (GstAmcBuffer * buffer) |
| { |
| JNIEnv *env; |
| GstAmcBuffer *ret; |
| |
| g_return_val_if_fail (buffer != NULL, NULL); |
| |
| env = gst_amc_jni_get_env (); |
| |
| ret = g_new0 (GstAmcBuffer, 1); |
| |
| ret->object = gst_amc_jni_object_ref (env, buffer->object); |
| ret->data = buffer->data; |
| ret->size = buffer->size; |
| |
| return ret; |
| } |
| |
| gboolean |
| gst_amc_buffer_get_position_and_limit (GstAmcBuffer * buffer, GError ** err, |
| gint * position, gint * limit) |
| { |
| JNIEnv *env; |
| |
| g_return_val_if_fail (buffer != NULL, FALSE); |
| g_return_val_if_fail (buffer->object != NULL, FALSE); |
| |
| env = gst_amc_jni_get_env (); |
| |
| if (!gst_amc_jni_call_int_method (env, err, buffer->object, |
| java_nio_buffer.get_position, position)) |
| return FALSE; |
| |
| if (!gst_amc_jni_call_int_method (env, err, buffer->object, |
| java_nio_buffer.get_limit, limit)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| gboolean |
| gst_amc_buffer_set_position_and_limit (GstAmcBuffer * buffer, GError ** err, |
| gint position, gint limit) |
| { |
| JNIEnv *env; |
| jobject tmp; |
| |
| g_return_val_if_fail (buffer != NULL, FALSE); |
| g_return_val_if_fail (buffer->object != NULL, FALSE); |
| |
| env = gst_amc_jni_get_env (); |
| |
| if (!gst_amc_jni_call_object_method (env, err, buffer->object, |
| java_nio_buffer.set_limit, &tmp, limit)) |
| return FALSE; |
| |
| gst_amc_jni_object_local_unref (env, tmp); |
| |
| if (!gst_amc_jni_call_object_method (env, err, buffer->object, |
| java_nio_buffer.set_position, &tmp, position)) |
| return FALSE; |
| |
| gst_amc_jni_object_local_unref (env, tmp); |
| |
| return TRUE; |
| } |
| |
| gboolean |
| gst_amc_buffer_clear (GstAmcBuffer * buffer, GError ** err) |
| { |
| JNIEnv *env; |
| jobject tmp; |
| |
| g_return_val_if_fail (buffer != NULL, FALSE); |
| g_return_val_if_fail (buffer->object != NULL, FALSE); |
| |
| env = gst_amc_jni_get_env (); |
| |
| if (!gst_amc_jni_call_object_method (env, err, buffer->object, |
| java_nio_buffer.clear, &tmp)) |
| return FALSE; |
| |
| gst_amc_jni_object_local_unref (env, tmp); |
| |
| return TRUE; |
| } |