d7c9da43ec3efdf7fbf99d576404ae0cc97a9a6c
[platform/upstream/gstreamer.git] / sys / androidmedia / gstjniutils.c
1 /*
2  * Copyright (C) 2012, Collabora Ltd.
3  *   Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
4  * Copyright (C) 2013, Fluendo S.A.
5  *   Author: Andoni Morales <amorales@fluendo.com>
6  * Copyright (C) 2014, Sebastian Dröge <sebastian@centricular.com>
7  * Copyright (C) 2014, Collabora Ltd.
8  *   Author: Matthieu Bouron <matthieu.bouron@collabora.com>
9  * Copyright (C) 2015, Sebastian Dröge <sebastian@centricular.com>
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation
14  * version 2.1 of the License.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
24  *
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #include <gst/gst.h>
32 #include <pthread.h>
33 #include <gmodule.h>
34
35 #include "gstjniutils.h"
36
37 static GModule *java_module;
38 static jint (*get_created_java_vms) (JavaVM ** vmBuf, jsize bufLen,
39     jsize * nVMs);
40 static jint (*create_java_vm) (JavaVM ** p_vm, JNIEnv ** p_env, void *vm_args);
41 static JavaVM *java_vm;
42 static gboolean started_java_vm = FALSE;
43 static pthread_key_t current_jni_env;
44 static jobject (*get_class_loader) (void);
45
46 jclass
47 gst_amc_jni_get_class (JNIEnv * env, GError ** err, const gchar * name)
48 {
49   jclass tmp, ret = NULL;
50
51   GST_DEBUG ("Retrieving Java class %s", name);
52
53   tmp = (*env)->FindClass (env, name);
54   if ((*env)->ExceptionCheck (env) || !tmp) {
55     gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
56         GST_LIBRARY_ERROR_FAILED, "Failed to find class %s", name);
57     goto done;
58   }
59
60   ret = (*env)->NewGlobalRef (env, tmp);
61   if (!ret) {
62     GST_ERROR ("Failed to get %s class global reference", name);
63   }
64
65 done:
66   if (tmp)
67     (*env)->DeleteLocalRef (env, tmp);
68   tmp = NULL;
69
70   return ret;
71 }
72
73 jmethodID
74 gst_amc_jni_get_method_id (JNIEnv * env, GError ** err, jclass klass,
75     const gchar * name, const gchar * signature)
76 {
77   jmethodID ret;
78
79   ret = (*env)->GetMethodID (env, klass, name, signature);
80   if ((*env)->ExceptionCheck (env) || !ret) {
81     gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
82         GST_LIBRARY_ERROR_FAILED, "Failed to get method ID %s (%s)", name,
83         signature);
84   }
85   return ret;
86 }
87
88 jmethodID
89 gst_amc_jni_get_static_method_id (JNIEnv * env, GError ** err, jclass klass,
90     const gchar * name, const gchar * signature)
91 {
92   jmethodID ret;
93
94   ret = (*env)->GetStaticMethodID (env, klass, name, signature);
95   if ((*env)->ExceptionCheck (env) || !ret) {
96     gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
97         GST_LIBRARY_ERROR_FAILED, "Failed to get static method ID %s (%s)",
98         name, signature);
99   }
100   return ret;
101 }
102
103 jfieldID
104 gst_amc_jni_get_field_id (JNIEnv * env, GError ** err, jclass klass,
105     const gchar * name, const gchar * type)
106 {
107   jfieldID ret;
108
109   ret = (*env)->GetFieldID (env, klass, name, type);
110   if ((*env)->ExceptionCheck (env) || !ret) {
111     gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
112         GST_LIBRARY_ERROR_FAILED, "Failed to get field ID %s (%s)", name, type);
113   }
114   return ret;
115 }
116
117 jfieldID
118 gst_amc_jni_get_static_field_id (JNIEnv * env, GError ** err, jclass klass,
119     const gchar * name, const gchar * type)
120 {
121   jfieldID ret;
122
123   ret = (*env)->GetStaticFieldID (env, klass, name, type);
124   if ((*env)->ExceptionCheck (env) || !ret) {
125     gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
126         GST_LIBRARY_ERROR_FAILED, "Failed to get static field ID %s (%s)", name,
127         type);
128   }
129   return ret;
130 }
131
132 jobject
133 gst_amc_jni_new_object (JNIEnv * env, GError ** err, gboolean global,
134     jclass klass, jmethodID constructor, ...)
135 {
136   jobject tmp;
137   va_list args;
138
139   va_start (args, constructor);
140   tmp = (*env)->NewObjectV (env, klass, constructor, args);
141   va_end (args);
142
143   if ((*env)->ExceptionCheck (env) || !tmp) {
144     gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
145         GST_LIBRARY_ERROR_FAILED, "Failed to create object");
146     return NULL;
147   }
148
149   if (global)
150     return gst_amc_jni_object_make_global (env, tmp);
151   else
152     return tmp;
153 }
154
155 jobject
156 gst_amc_jni_new_object_from_static (JNIEnv * env, GError ** err,
157     gboolean global, jclass klass, jmethodID method, ...)
158 {
159   jobject tmp;
160   va_list args;
161
162   va_start (args, method);
163   tmp = (*env)->CallStaticObjectMethodV (env, klass, method, args);
164   va_end (args);
165
166   if ((*env)->ExceptionCheck (env) || !tmp) {
167     gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
168         GST_LIBRARY_ERROR_FAILED, "Failed to create object");
169     return NULL;
170   }
171
172   if (global)
173     return gst_amc_jni_object_make_global (env, tmp);
174   else
175     return tmp;
176 }
177
178 jobject
179 gst_amc_jni_object_make_global (JNIEnv * env, jobject object)
180 {
181   jobject ret;
182
183   ret = (*env)->NewGlobalRef (env, object);
184   if (!ret) {
185     GST_ERROR ("Failed to create global reference");
186   }
187   gst_amc_jni_object_local_unref (env, object);
188
189   return ret;
190 }
191
192 jobject
193 gst_amc_jni_object_ref (JNIEnv * env, jobject object)
194 {
195   jobject ret;
196
197   ret = (*env)->NewGlobalRef (env, object);
198   if (!ret) {
199     GST_ERROR ("Failed to create global reference");
200   }
201   return ret;
202 }
203
204 void
205 gst_amc_jni_object_unref (JNIEnv * env, jobject object)
206 {
207   g_return_if_fail (object != NULL);
208
209   (*env)->DeleteGlobalRef (env, object);
210 }
211
212 void
213 gst_amc_jni_object_local_unref (JNIEnv * env, jobject object)
214 {
215   g_return_if_fail (object != NULL);
216
217   (*env)->DeleteLocalRef (env, object);
218 }
219
220 jstring
221 gst_amc_jni_string_from_gchar (JNIEnv * env, GError ** err,
222     gboolean global, const gchar * string)
223 {
224   jstring tmp;
225
226   tmp = (*env)->NewStringUTF (env, string);
227   if ((*env)->ExceptionCheck (env)) {
228     gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
229         GST_LIBRARY_ERROR_FAILED, "Failed to call Java method");
230     tmp = NULL;
231   }
232
233   if (global)
234     return gst_amc_jni_object_make_global (env, tmp);
235   else
236     return tmp;
237 }
238
239 gchar *
240 gst_amc_jni_string_to_gchar (JNIEnv * env, jstring string, gboolean release)
241 {
242   const gchar *s = NULL;
243   gchar *ret = NULL;
244
245   s = (*env)->GetStringUTFChars (env, string, NULL);
246   if (!s) {
247     GST_ERROR ("Failed to convert string to UTF8");
248     goto done;
249   }
250
251   ret = g_strdup (s);
252   (*env)->ReleaseStringUTFChars (env, string, s);
253
254 done:
255   if (release) {
256     (*env)->DeleteLocalRef (env, string);
257   }
258   return ret;
259 }
260
261 /* getExceptionSummary() and getStackTrace() taken from Android's
262  *   platform/libnativehelper/JNIHelp.cpp
263  * Modified to work with normal C strings and without C++.
264  *
265  * Copyright (C) 2006 The Android Open Source Project
266  *
267  * Licensed under the Apache License, Version 2.0 (the "License");
268  * you may not use this file except in compliance with the License.
269  * You may obtain a copy of the License at
270  *
271  * http://www.apache.org/licenses/LICENSE-2.0
272  *
273  * Unless required by applicable law or agreed to in writing, software
274  * distributed under the License is distributed on an "AS IS" BASIS,
275  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
276  * See the License for the specific language governing permissions and
277  * limitations under the License.
278  */
279
280 /*
281  * Returns a human-readable summary of an exception object. The buffer will
282  * be populated with the "binary" class name and, if present, the
283  * exception message.
284  */
285 static gchar *
286 getExceptionSummary (JNIEnv * env, jthrowable exception)
287 {
288   GString *gs = g_string_new ("");
289   jclass exceptionClass = NULL, classClass = NULL;
290   jmethodID classGetNameMethod, getMessage;
291   jstring classNameStr = NULL, messageStr = NULL;
292   const char *classNameChars, *messageChars;
293
294   /* get the name of the exception's class */
295   exceptionClass = (*env)->GetObjectClass (env, exception);
296   classClass = (*env)->GetObjectClass (env, exceptionClass);
297   classGetNameMethod =
298       (*env)->GetMethodID (env, classClass, "getName", "()Ljava/lang/String;");
299
300   classNameStr =
301       (jstring) (*env)->CallObjectMethod (env, exceptionClass,
302       classGetNameMethod);
303
304   if (classNameStr == NULL) {
305     if ((*env)->ExceptionCheck (env))
306       (*env)->ExceptionClear (env);
307     g_string_append (gs, "<error getting class name>");
308     goto done;
309   }
310
311   classNameChars = (*env)->GetStringUTFChars (env, classNameStr, NULL);
312   if (classNameChars == NULL) {
313     if ((*env)->ExceptionCheck (env))
314       (*env)->ExceptionClear (env);
315     g_string_append (gs, "<error getting class name UTF-8>");
316     goto done;
317   }
318
319   g_string_append (gs, classNameChars);
320
321   (*env)->ReleaseStringUTFChars (env, classNameStr, classNameChars);
322
323   /* if the exception has a detail message, get that */
324   getMessage =
325       (*env)->GetMethodID (env, exceptionClass, "getMessage",
326       "()Ljava/lang/String;");
327   messageStr = (jstring) (*env)->CallObjectMethod (env, exception, getMessage);
328   if (messageStr == NULL) {
329     if ((*env)->ExceptionCheck (env))
330       (*env)->ExceptionClear (env);
331     goto done;
332   }
333   g_string_append (gs, ": ");
334
335   messageChars = (*env)->GetStringUTFChars (env, messageStr, NULL);
336   if (messageChars != NULL) {
337     g_string_append (gs, messageChars);
338     (*env)->ReleaseStringUTFChars (env, messageStr, messageChars);
339   } else {
340     if ((*env)->ExceptionCheck (env))
341       (*env)->ExceptionClear (env);
342     g_string_append (gs, "<error getting message>");
343   }
344
345 done:
346   if (exceptionClass)
347     (*env)->DeleteLocalRef (env, exceptionClass);
348   if (classClass)
349     (*env)->DeleteLocalRef (env, classClass);
350   if (classNameStr)
351     (*env)->DeleteLocalRef (env, classNameStr);
352   if (messageStr)
353     (*env)->DeleteLocalRef (env, messageStr);
354
355   return g_string_free (gs, FALSE);
356 }
357
358 /*
359  * Returns an exception (with stack trace) as a string.
360  */
361 static gchar *
362 getStackTrace (JNIEnv * env, jthrowable exception)
363 {
364   GString *gs = g_string_new ("");
365   jclass stringWriterClass = NULL, printWriterClass = NULL;
366   jclass exceptionClass = NULL;
367   jmethodID stringWriterCtor, stringWriterToStringMethod;
368   jmethodID printWriterCtor, printStackTraceMethod;
369   jobject stringWriter = NULL, printWriter = NULL;
370   jstring messageStr = NULL;
371   const char *utfChars;
372
373   stringWriterClass = (*env)->FindClass (env, "java/io/StringWriter");
374
375   if (stringWriterClass == NULL) {
376     g_string_append (gs, "<error getting java.io.StringWriter class>");
377     goto done;
378   }
379
380   stringWriterCtor =
381       (*env)->GetMethodID (env, stringWriterClass, "<init>", "()V");
382   stringWriterToStringMethod =
383       (*env)->GetMethodID (env, stringWriterClass, "toString",
384       "()Ljava/lang/String;");
385
386   printWriterClass = (*env)->FindClass (env, "java/io/PrintWriter");
387   if (printWriterClass == NULL) {
388     g_string_append (gs, "<error getting java.io.PrintWriter class>");
389     goto done;
390   }
391
392   printWriterCtor =
393       (*env)->GetMethodID (env, printWriterClass, "<init>",
394       "(Ljava/io/Writer;)V");
395   stringWriter = (*env)->NewObject (env, stringWriterClass, stringWriterCtor);
396   if (stringWriter == NULL) {
397     if ((*env)->ExceptionCheck (env))
398       (*env)->ExceptionClear (env);
399     g_string_append (gs, "<error creating new StringWriter instance>");
400     goto done;
401   }
402
403   printWriter =
404       (*env)->NewObject (env, printWriterClass, printWriterCtor, stringWriter);
405   if (printWriter == NULL) {
406     if ((*env)->ExceptionCheck (env))
407       (*env)->ExceptionClear (env);
408     g_string_append (gs, "<error creating new PrintWriter instance>");
409     goto done;
410   }
411
412   exceptionClass = (*env)->GetObjectClass (env, exception);
413   printStackTraceMethod =
414       (*env)->GetMethodID (env, exceptionClass, "printStackTrace",
415       "(Ljava/io/PrintWriter;)V");
416   (*env)->CallVoidMethod (env, exception, printStackTraceMethod, printWriter);
417   if ((*env)->ExceptionCheck (env)) {
418     (*env)->ExceptionClear (env);
419     g_string_append (gs, "<exception while printing stack trace>");
420     goto done;
421   }
422
423   messageStr = (jstring) (*env)->CallObjectMethod (env, stringWriter,
424       stringWriterToStringMethod);
425   if (messageStr == NULL) {
426     if ((*env)->ExceptionCheck (env))
427       (*env)->ExceptionClear (env);
428     g_string_append (gs, "<failed to call StringWriter.toString()>");
429     goto done;
430   }
431
432   utfChars = (*env)->GetStringUTFChars (env, messageStr, NULL);
433   if (utfChars == NULL) {
434     if ((*env)->ExceptionCheck (env))
435       (*env)->ExceptionClear (env);
436     g_string_append (gs, "<failed to get UTF chars for message>");
437     goto done;
438   }
439
440   g_string_append (gs, utfChars);
441
442   (*env)->ReleaseStringUTFChars (env, messageStr, utfChars);
443
444 done:
445   if (stringWriterClass)
446     (*env)->DeleteLocalRef (env, stringWriterClass);
447   if (printWriterClass)
448     (*env)->DeleteLocalRef (env, printWriterClass);
449   if (exceptionClass)
450     (*env)->DeleteLocalRef (env, exceptionClass);
451   if (stringWriter)
452     (*env)->DeleteLocalRef (env, stringWriter);
453   if (printWriter)
454     (*env)->DeleteLocalRef (env, printWriter);
455   if (messageStr)
456     (*env)->DeleteLocalRef (env, messageStr);
457
458   return g_string_free (gs, FALSE);
459 }
460
461 static JNIEnv *
462 gst_amc_jni_attach_current_thread (void)
463 {
464   JNIEnv *env;
465   JavaVMAttachArgs args;
466   gint ret;
467
468   GST_DEBUG ("Attaching thread %p", g_thread_self ());
469   args.version = JNI_VERSION_1_6;
470   args.name = NULL;
471   args.group = NULL;
472
473   if ((ret = (*java_vm)->AttachCurrentThread (java_vm, &env, &args)) != JNI_OK) {
474     GST_ERROR ("Failed to attach current thread: %d", ret);
475     return NULL;
476   }
477
478   return env;
479 }
480
481 static void
482 gst_amc_jni_detach_current_thread (void *env)
483 {
484   gint ret;
485
486   GST_DEBUG ("Detaching thread %p", g_thread_self ());
487   if ((ret = (*java_vm)->DetachCurrentThread (java_vm)) != JNI_OK) {
488     GST_DEBUG ("Failed to detach current thread: %d", ret);
489   }
490 }
491
492 static JavaVM *
493 get_application_java_vm (void)
494 {
495   GModule *module = NULL;
496   JavaVM *(*get_java_vm) (void);
497   JavaVM *vm = NULL;
498
499   module = g_module_open (NULL, G_MODULE_BIND_LOCAL);
500
501   if (!module) {
502     return NULL;
503   }
504
505   if (g_module_symbol (module, "gst_android_get_java_vm",
506           (gpointer *) & get_java_vm) && get_java_vm) {
507     vm = get_java_vm ();
508   }
509
510   g_module_close (module);
511
512   return vm;
513 }
514
515 static gboolean
516 check_nativehelper (void)
517 {
518   GModule *module;
519   void **jni_invocation = NULL;
520   gboolean ret = FALSE;
521
522   module = g_module_open (NULL, G_MODULE_BIND_LOCAL);
523   if (!module)
524     return ret;
525
526   /* Check if libnativehelper is loaded in the process and if
527    * it has these awful wrappers for JNI_CreateJavaVM and
528    * JNI_GetCreatedJavaVMs that crash the app if you don't
529    * create a JniInvocation instance first. If it isn't we
530    * just fail here and don't initialize anything.
531    * See this code for reference:
532    * https://android.googlesource.com/platform/libnativehelper/+/master/JniInvocation.cpp
533    */
534   if (!g_module_symbol (module, "_ZN13JniInvocation15jni_invocation_E",
535           (gpointer *) & jni_invocation)) {
536     ret = TRUE;
537   } else {
538     ret = (jni_invocation != NULL && *jni_invocation != NULL);
539   }
540
541   g_module_close (module);
542
543   return ret;
544 }
545
546 static gboolean
547 load_java_module (const gchar * name)
548 {
549   java_module = g_module_open (name, G_MODULE_BIND_LOCAL);
550   if (!java_module)
551     goto load_failed;
552
553   if (!g_module_symbol (java_module, "JNI_CreateJavaVM",
554           (gpointer *) & create_java_vm)) {
555     GST_ERROR ("Could not find 'JNI_CreateJavaVM' in '%s': %s",
556         GST_STR_NULL (name), g_module_error ());
557     create_java_vm = NULL;
558   }
559
560   if (!g_module_symbol (java_module, "JNI_GetCreatedJavaVMs",
561           (gpointer *) & get_created_java_vms))
562     goto symbol_error;
563
564   return TRUE;
565
566 load_failed:
567   {
568     GST_ERROR ("Failed to load Java module '%s': %s", GST_STR_NULL (name),
569         g_module_error ());
570     return FALSE;
571   }
572 symbol_error:
573   {
574     GST_ERROR ("Failed to locate required JNI symbols in '%s': %s",
575         GST_STR_NULL (name), g_module_error ());
576     g_module_close (java_module);
577     java_module = NULL;
578     return FALSE;
579   }
580 }
581
582 static gboolean
583 check_application_class_loader (void)
584 {
585   gboolean ret = TRUE;
586   GModule *module = NULL;
587
588   module = g_module_open (NULL, G_MODULE_BIND_LOCAL);
589   if (!module) {
590     return FALSE;
591   }
592   if (!g_module_symbol (module, "gst_android_get_application_class_loader",
593           (gpointer *) & get_class_loader)) {
594     ret = FALSE;
595   }
596
597   g_module_close (module);
598
599   return ret;
600 }
601
602 static gboolean
603 initialize_classes (void)
604 {
605   if (!check_application_class_loader ()) {
606     GST_ERROR ("Could not find application class loader provider");
607     return FALSE;
608   }
609
610   return TRUE;
611 }
612
613 static gboolean
614 gst_amc_jni_initialize_java_vm (void)
615 {
616   jsize n_vms;
617   gint ret;
618
619   if (java_vm) {
620     GST_DEBUG ("Java VM already provided by the application");
621     return initialize_classes ();
622   }
623
624   java_vm = get_application_java_vm ();
625   if (java_vm) {
626     GST_DEBUG ("Java VM successfully requested from the application");
627     return initialize_classes ();
628   }
629
630   /* Returns TRUE if we can safely
631    * a) get the current VMs and
632    * b) start a VM if none is started yet
633    *
634    * FIXME: On Android >= 4.4 we won't be able to safely start a
635    * VM on our own without using private C++ API!
636    */
637   if (!check_nativehelper ()) {
638     GST_ERROR ("Can't safely check for VMs or start a VM");
639     return FALSE;
640   }
641
642   if (!load_java_module (NULL)) {
643     if (!load_java_module ("libdvm"))
644       return FALSE;
645   }
646
647   n_vms = 0;
648   if ((ret = get_created_java_vms (&java_vm, 1, &n_vms)) != JNI_OK)
649     goto get_created_failed;
650
651   if (n_vms > 0) {
652     GST_DEBUG ("Successfully got existing Java VM %p", java_vm);
653   } else if (create_java_vm) {
654     JNIEnv *env;
655     JavaVMInitArgs vm_args;
656     JavaVMOption options[4];
657
658     GST_DEBUG ("Found no existing Java VM, trying to start one");
659
660     options[0].optionString = "-verbose:jni";
661     options[1].optionString = "-verbose:gc";
662     options[2].optionString = "-Xcheck:jni";
663     options[3].optionString = "-Xdebug";
664
665     vm_args.version = JNI_VERSION_1_4;
666     vm_args.options = options;
667     vm_args.nOptions = 4;
668     vm_args.ignoreUnrecognized = JNI_TRUE;
669     if ((ret = create_java_vm (&java_vm, &env, &vm_args)) != JNI_OK)
670       goto create_failed;
671     GST_DEBUG ("Successfully created Java VM %p", java_vm);
672
673     started_java_vm = TRUE;
674   } else {
675     GST_ERROR ("JNI_CreateJavaVM not available");
676     java_vm = NULL;
677   }
678
679   if (java_vm == NULL)
680     return FALSE;
681
682   return initialize_classes ();
683
684 get_created_failed:
685   {
686     GST_ERROR ("Failed to get already created VMs: %d", ret);
687     g_module_close (java_module);
688     java_module = NULL;
689     return FALSE;
690   }
691 create_failed:
692   {
693     GST_ERROR ("Failed to create a Java VM: %d", ret);
694     g_module_close (java_module);
695     java_module = NULL;
696     return FALSE;
697   }
698 }
699
700 static void
701 gst_amc_jni_set_error_string (JNIEnv * env, GError ** err, GQuark domain,
702     gint code, const gchar * message)
703 {
704   jthrowable exception;
705
706   if (!err) {
707     if ((*env)->ExceptionCheck (env))
708       (*env)->ExceptionClear (env);
709     return;
710   }
711
712   if ((*env)->ExceptionCheck (env)) {
713     if ((exception = (*env)->ExceptionOccurred (env))) {
714       gchar *exception_description, *exception_stacktrace;
715
716       /* Clear exception so that we can call Java methods again */
717       (*env)->ExceptionClear (env);
718
719       exception_description = getExceptionSummary (env, exception);
720       exception_stacktrace = getStackTrace (env, exception);
721       g_set_error (err, domain, code, "%s: %s\n%s", message,
722           exception_description, exception_stacktrace);
723       g_free (exception_description);
724       g_free (exception_stacktrace);
725
726       (*env)->DeleteLocalRef (env, exception);
727     } else {
728       (*env)->ExceptionClear (env);
729       g_set_error (err, domain, code, "%s", message);
730     }
731   } else {
732     g_set_error (err, domain, code, "%s", message);
733   }
734 }
735
736 G_GNUC_PRINTF (5, 6)
737      void gst_amc_jni_set_error (JNIEnv * env, GError ** err, GQuark domain,
738     gint code, const gchar * format, ...)
739 {
740   gchar *message;
741   va_list var_args;
742
743   va_start (var_args, format);
744   message = g_strdup_vprintf (format, var_args);
745   va_end (var_args);
746
747   gst_amc_jni_set_error_string (env, err, domain, code, message);
748
749   g_free (message);
750 }
751
752 static gpointer
753 gst_amc_jni_initialize_internal (gpointer data)
754 {
755   pthread_key_create (&current_jni_env, gst_amc_jni_detach_current_thread);
756   return gst_amc_jni_initialize_java_vm ()? GINT_TO_POINTER (1) : NULL;
757 }
758
759 /* Allow the application to set the Java VM */
760 void
761 gst_amc_jni_set_java_vm (JavaVM * vm)
762 {
763   GST_DEBUG ("Application provides Java VM %p", vm);
764   java_vm = vm;
765 }
766
767 gboolean
768 gst_amc_jni_initialize (void)
769 {
770   GOnce once = G_ONCE_INIT;
771
772   g_once (&once, gst_amc_jni_initialize_internal, NULL);
773   return once.retval != NULL;
774 }
775
776 JNIEnv *
777 gst_amc_jni_get_env (void)
778 {
779   JNIEnv *env;
780
781   if ((env = pthread_getspecific (current_jni_env)) == NULL) {
782     env = gst_amc_jni_attach_current_thread ();
783     pthread_setspecific (current_jni_env, env);
784   }
785
786   return env;
787 }
788
789 gboolean
790 gst_amc_jni_is_vm_started (void)
791 {
792   return started_java_vm;
793 }
794
795 jclass
796 gst_amc_jni_get_application_class (JNIEnv * env, const gchar * name,
797     GError ** err)
798 {
799   jclass tmp = NULL;
800   jclass class = NULL;
801   jstring name_jstr = NULL;
802
803   jobject class_loader = NULL;
804   jclass class_loader_cls = NULL;
805   jmethodID load_class_id = 0;
806
807   GST_LOG ("attempting to retrieve class %s", name);
808
809   if (!get_class_loader) {
810     g_set_error (err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,
811         "Could not retreive application class loader function");
812     goto done;
813   }
814
815   class_loader = get_class_loader ();
816   if (!class_loader) {
817     g_set_error (err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,
818         "Could not retreive application class loader");
819     goto done;
820   }
821
822   class_loader_cls = (*env)->GetObjectClass (env, class_loader);
823   if (!class_loader_cls) {
824     g_set_error (err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,
825         "Could not retreive application class loader java class");
826     goto done;
827   }
828
829   load_class_id =
830       gst_amc_jni_get_method_id (env, err, class_loader_cls, "loadClass",
831       "(Ljava/lang/String;)Ljava/lang/Class;");
832   if (!load_class_id) {
833     goto done;
834   }
835
836   name_jstr = gst_amc_jni_string_from_gchar (env, err, FALSE, name);
837   if (!name_jstr) {
838     goto done;
839   }
840
841   if (gst_amc_jni_call_object_method (env, err, class_loader,
842           load_class_id, &tmp, name_jstr)) {
843     class = gst_amc_jni_object_make_global (env, tmp);
844   }
845
846 done:
847   gst_amc_jni_object_local_unref (env, name_jstr);
848   gst_amc_jni_object_local_unref (env, class_loader_cls);
849
850   return class;
851 }
852
853 #define CALL_STATIC_TYPE_METHOD(_type, _name,  _jname)                                                     \
854 gboolean gst_amc_jni_call_static_##_name##_method (JNIEnv *env, GError ** err, jclass klass, jmethodID methodID, _type * value, ...)   \
855   {                                                                                                          \
856     gboolean ret = TRUE;                                                                                     \
857     va_list args;                                                                                            \
858     va_start(args, value);                                                                                \
859     *value = (*env)->CallStatic##_jname##MethodV(env, klass, methodID, args);                                \
860     if ((*env)->ExceptionCheck (env)) {                                                                      \
861       gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,                               \
862           "Failed to call static Java method");                                                         \
863       ret = FALSE;                                                                                           \
864     }                                                                                                        \
865     va_end(args);                                                                                            \
866     return ret;                                                                                              \
867   }
868
869 CALL_STATIC_TYPE_METHOD (gboolean, boolean, Boolean);
870 CALL_STATIC_TYPE_METHOD (gint8, byte, Byte);
871 CALL_STATIC_TYPE_METHOD (gshort, short, Short);
872 CALL_STATIC_TYPE_METHOD (gint, int, Int);
873 CALL_STATIC_TYPE_METHOD (gchar, char, Char);
874 CALL_STATIC_TYPE_METHOD (gint64, long, Long);
875 CALL_STATIC_TYPE_METHOD (gfloat, float, Float);
876 CALL_STATIC_TYPE_METHOD (gdouble, double, Double);
877 CALL_STATIC_TYPE_METHOD (jobject, object, Object);
878
879 gboolean
880 gst_amc_jni_call_static_void_method (JNIEnv * env, GError ** err, jclass klass,
881     jmethodID methodID, ...)
882 {
883   gboolean ret = TRUE;
884   va_list args;
885   va_start (args, methodID);
886
887   (*env)->CallStaticVoidMethodV (env, klass, methodID, args);
888   if ((*env)->ExceptionCheck (env)) {
889     gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
890         GST_LIBRARY_ERROR_FAILED, "Failed to call static Java method");
891     ret = FALSE;
892   }
893   va_end (args);
894   return ret;
895 }
896
897 #define CALL_TYPE_METHOD(_type, _name,  _jname)                                                              \
898 gboolean gst_amc_jni_call_##_name##_method (JNIEnv *env, GError ** err, jobject obj, jmethodID methodID, _type *value, ...)   \
899   {                                                                                                          \
900     gboolean ret = TRUE;                                                                                     \
901     va_list args;                                                                                            \
902     va_start(args, value);                                                                                \
903     *value = (*env)->Call##_jname##MethodV(env, obj, methodID, args);                                        \
904     if ((*env)->ExceptionCheck (env)) {                                                                      \
905       gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,                               \
906           "Failed to call Java method");                                                                \
907       ret = FALSE;                                                                                           \
908     }                                                                                                        \
909     va_end(args);                                                                                            \
910     return ret;                                                                                              \
911   }
912
913 CALL_TYPE_METHOD (gboolean, boolean, Boolean);
914 CALL_TYPE_METHOD (gint8, byte, Byte);
915 CALL_TYPE_METHOD (gshort, short, Short);
916 CALL_TYPE_METHOD (gint, int, Int);
917 CALL_TYPE_METHOD (gchar, char, Char);
918 CALL_TYPE_METHOD (gint64, long, Long);
919 CALL_TYPE_METHOD (gfloat, float, Float);
920 CALL_TYPE_METHOD (gdouble, double, Double);
921 CALL_TYPE_METHOD (jobject, object, Object);
922
923 gboolean
924 gst_amc_jni_call_void_method (JNIEnv * env, GError ** err, jobject obj,
925     jmethodID methodID, ...)
926 {
927   gboolean ret = TRUE;
928   va_list args;
929   va_start (args, methodID);
930
931   (*env)->CallVoidMethodV (env, obj, methodID, args);
932   if ((*env)->ExceptionCheck (env)) {
933     gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR,
934         GST_LIBRARY_ERROR_FAILED, "Failed to call Java method");
935     ret = FALSE;
936   }
937   va_end (args);
938   return ret;
939 }
940
941 #define GET_TYPE_FIELD(_type, _name, _jname)                                               \
942 gboolean gst_amc_jni_get_##_name##_field (JNIEnv *env, GError ** err, jobject obj, jfieldID fieldID, _type *value)   \
943   {                                                                                                 \
944     gboolean ret = TRUE;                                                                            \
945                                                                                                     \
946     *value = (*env)->Get##_jname##Field(env, obj, fieldID);                                         \
947     if ((*env)->ExceptionCheck (env)) {                                                             \
948       gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,                      \
949           "Failed to get Java field");                                                         \
950       ret = FALSE;                                                                                  \
951     }                                                                                               \
952     return ret;                                                                                     \
953   }
954
955 GET_TYPE_FIELD (gboolean, boolean, Boolean);
956 GET_TYPE_FIELD (gint8, byte, Byte);
957 GET_TYPE_FIELD (gshort, short, Short);
958 GET_TYPE_FIELD (gint, int, Int);
959 GET_TYPE_FIELD (gchar, char, Char);
960 GET_TYPE_FIELD (gint64, long, Long);
961 GET_TYPE_FIELD (gfloat, float, Float);
962 GET_TYPE_FIELD (gdouble, double, Double);
963 GET_TYPE_FIELD (jobject, object, Object);
964
965 #define GET_STATIC_TYPE_FIELD(_type, _name, _jname)                                               \
966 gboolean gst_amc_jni_get_static_##_name##_field (JNIEnv *env, GError ** err, jclass klass, jfieldID fieldID, _type *value)   \
967   {                                                                                                 \
968     gboolean ret = TRUE;                                                                            \
969                                                                                                     \
970     *value = (*env)->GetStatic##_jname##Field(env, klass, fieldID);                                 \
971     if ((*env)->ExceptionCheck (env)) {                                                             \
972       gst_amc_jni_set_error (env, err, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,                      \
973           "Failed to get static Java field");                                                  \
974       ret = FALSE;                                                                                  \
975     }                                                                                               \
976     return ret;                                                                                     \
977   }
978
979 GET_STATIC_TYPE_FIELD (gboolean, boolean, Boolean);
980 GET_STATIC_TYPE_FIELD (gint8, byte, Byte);
981 GET_STATIC_TYPE_FIELD (gshort, short, Short);
982 GET_STATIC_TYPE_FIELD (gint, int, Int);
983 GET_STATIC_TYPE_FIELD (gchar, char, Char);
984 GET_STATIC_TYPE_FIELD (gint64, long, Long);
985 GET_STATIC_TYPE_FIELD (gfloat, float, Float);
986 GET_STATIC_TYPE_FIELD (gdouble, double, Double);
987 GET_STATIC_TYPE_FIELD (jobject, object, Object);