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