blob: a5b8e0d721a859d609d23616c1ed680d1f05f408 [file] [log] [blame]
/*
* 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 JavaVM *
get_application_java_vm (void)
{
GModule *module = NULL;
JavaVM *(*get_java_vm) (void);
JavaVM *vm = NULL;
module = g_module_open (NULL, G_MODULE_BIND_LOCAL);
if (!module) {
return NULL;
}
if (g_module_symbol (module, "gst_android_get_java_vm",
(gpointer *) & get_java_vm) && get_java_vm) {
vm = get_java_vm ();
}
g_module_close (module);
return vm;
}
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 ();
}
java_vm = get_application_java_vm ();
if (java_vm) {
GST_DEBUG ("Java VM successfully requested from 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 (&current_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;
}