1 /* This library is free software; you can redistribute it and/or
2 * modify it under the terms of the GNU Library General Public
3 * License as published by the Free Software Foundation; either
4 * version 2 of the License, or (at your option) any later version.
6 * This library is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9 * Library General Public License for more details.
11 * You should have received a copy of the GNU Library General Public
12 * License along with this library; if not, write to the
13 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
14 * Boston, MA 02110-1301, USA.
21 #include "gst_private.h"
25 #include <android/log.h>
28 /* AMC plugin uses g_module_symbol() to find those symbols */
29 GST_API jobject gst_android_get_application_context (void);
30 GST_API jobject gst_android_get_application_class_loader (void);
31 GST_API JavaVM *gst_android_get_java_vm (void);
33 /* XXX: Workaround for Android <21 making signal() an inline function
34 * around bsd_signal(), and Android >= 21 not having any bsd_signal()
35 * symbol but only signal().
36 * See https://bugzilla.gnome.org/show_bug.cgi?id=766235
39 load_real_signal (gpointer data)
44 module = g_module_open ("libc.so", G_MODULE_BIND_LOCAL);
45 g_module_symbol (module, "signal", &ret);
47 /* As fallback, let's try bsd_signal */
49 g_warning ("Can't find signal(3) in libc.so!");
50 g_module_symbol (module, "bsd_signal", &ret);
53 g_module_close (module);
58 __sighandler_t bsd_signal (int signum, __sighandler_t handler)
59 __attribute__ ((weak));
61 bsd_signal (int signum, __sighandler_t handler)
63 static GOnce gonce = G_ONCE_INIT;
64 __sighandler_t (*real_signal) (int signum, __sighandler_t handler);
66 g_once (&gonce, load_real_signal, NULL);
68 real_signal = gonce.retval;
69 g_assert (real_signal != NULL);
71 return real_signal (signum, handler);
74 static jobject _context = NULL;
75 static jobject _class_loader = NULL;
76 static JavaVM *_java_vm = NULL;
77 static GstClockTime _priv_gst_info_start_time;
80 glib_print_handler (const gchar * string)
82 __android_log_print (ANDROID_LOG_INFO, "GLib+stdout", "%s", string);
86 glib_printerr_handler (const gchar * string)
88 __android_log_print (ANDROID_LOG_ERROR, "GLib+stderr", "%s", string);
92 /* Based on GLib's default handler */
93 #define CHAR_IS_SAFE(wc) (!((wc < 0x20 && wc != '\t' && wc != '\n' && wc != '\r') || \
95 (wc >= 0x80 && wc < 0xa0)))
96 #define FORMAT_UNSIGNED_BUFSIZE ((GLIB_SIZEOF_LONG * 3) + 3)
97 #define STRING_BUFFER_SIZE (FORMAT_UNSIGNED_BUFSIZE + 32)
98 #define ALERT_LEVELS (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)
99 #define DEFAULT_LEVELS (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE)
100 #define INFO_LEVELS (G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG)
103 escape_string (GString * string)
105 const char *p = string->str;
108 while (p < string->str + string->len) {
111 wc = g_utf8_get_char_validated (p, -1);
112 if (wc == (gunichar) - 1 || wc == (gunichar) - 2) {
116 pos = p - string->str;
118 /* Emit invalid UTF-8 as hex escapes
120 tmp = g_strdup_printf ("\\x%02x", (guint) (guchar) * p);
121 g_string_erase (string, pos, 1);
122 g_string_insert (string, pos, tmp);
124 p = string->str + (pos + 4); /* Skip over escape sequence */
130 safe = *(p + 1) == '\n';
132 safe = CHAR_IS_SAFE (wc);
139 pos = p - string->str;
141 /* Largest char we escape is 0x0a, so we don't have to worry
142 * about 8-digit \Uxxxxyyyy
144 tmp = g_strdup_printf ("\\u%04x", wc);
145 g_string_erase (string, pos, g_utf8_next_char (p) - p);
146 g_string_insert (string, pos, tmp);
149 p = string->str + (pos + 6); /* Skip over escape sequence */
151 p = g_utf8_next_char (p);
156 glib_log_handler (const gchar * log_domain, GLogLevelFlags log_level,
157 const gchar * message, gpointer user_data)
161 const gchar *domains;
162 gint android_log_level;
165 if ((log_level & DEFAULT_LEVELS) || (log_level >> G_LOG_LEVEL_USER_SHIFT))
168 domains = g_getenv ("G_MESSAGES_DEBUG");
169 if (((log_level & INFO_LEVELS) == 0) ||
171 (strcmp (domains, "all") != 0 && (!log_domain
172 || !strstr (domains, log_domain))))
178 tag = g_strdup_printf ("GLib+%s", log_domain);
180 tag = g_strdup ("GLib");
182 switch (log_level & G_LOG_LEVEL_MASK) {
183 case G_LOG_LEVEL_ERROR:
184 case G_LOG_LEVEL_CRITICAL:
185 android_log_level = ANDROID_LOG_ERROR;
187 case G_LOG_LEVEL_WARNING:
188 android_log_level = ANDROID_LOG_WARN;
190 case G_LOG_LEVEL_MESSAGE:
191 case G_LOG_LEVEL_INFO:
192 android_log_level = ANDROID_LOG_INFO;
194 case G_LOG_LEVEL_DEBUG:
195 android_log_level = ANDROID_LOG_DEBUG;
198 android_log_level = ANDROID_LOG_VERBOSE;
202 gstring = g_string_new (NULL);
204 g_string_append (gstring, "(NULL) message");
206 GString *msg = g_string_new (message);
208 g_string_append (gstring, msg->str);
209 g_string_free (msg, TRUE);
211 string = g_string_free (gstring, FALSE);
213 __android_log_print (android_log_level, tag, "%s", string);
220 gst_debug_logcat (GstDebugCategory * category, GstDebugLevel level,
221 const gchar * file, const gchar * function, gint line,
222 GObject * object, GstDebugMessage * message, gpointer unused)
224 GstClockTime elapsed;
225 gint android_log_level;
228 if (level > gst_debug_category_get_threshold (category))
231 elapsed = GST_CLOCK_DIFF (_priv_gst_info_start_time,
232 gst_util_get_timestamp ());
235 case GST_LEVEL_ERROR:
236 android_log_level = ANDROID_LOG_ERROR;
238 case GST_LEVEL_WARNING:
239 android_log_level = ANDROID_LOG_WARN;
241 case GST_LEVEL_FIXME:
243 android_log_level = ANDROID_LOG_INFO;
245 case GST_LEVEL_DEBUG:
246 android_log_level = ANDROID_LOG_DEBUG;
249 android_log_level = ANDROID_LOG_VERBOSE;
253 tag = g_strdup_printf ("GStreamer+%s",
254 gst_debug_category_get_name (category));
259 if (GST_IS_PAD (object) && GST_OBJECT_NAME (object)) {
260 obj = g_strdup_printf ("<%s:%s>", GST_DEBUG_PAD_NAME (object));
261 } else if (GST_IS_OBJECT (object) && GST_OBJECT_NAME (object)) {
262 obj = g_strdup_printf ("<%s>", GST_OBJECT_NAME (object));
263 } else if (G_IS_OBJECT (object)) {
264 obj = g_strdup_printf ("<%s@%p>", G_OBJECT_TYPE_NAME (object), object);
266 obj = g_strdup_printf ("<%p>", object);
269 __android_log_print (android_log_level, tag,
270 "%" GST_TIME_FORMAT " %p %s:%d:%s:%s %s\n",
271 GST_TIME_ARGS (elapsed), g_thread_self (),
272 file, line, function, obj, gst_debug_message_get (message));
276 __android_log_print (android_log_level, tag,
277 "%" GST_TIME_FORMAT " %p %s:%d:%s %s\n",
278 GST_TIME_ARGS (elapsed), g_thread_self (),
279 file, line, function, gst_debug_message_get (message));
285 get_application_dirs (JNIEnv * env, jobject context, gchar ** cache_dir,
288 jclass context_class;
289 jmethodID get_cache_dir_id, get_files_dir_id;
291 jmethodID get_absolute_path_id;
294 const gchar *abs_path_str;
296 *cache_dir = *files_dir = NULL;
298 context_class = (*env)->GetObjectClass (env, context);
299 if (!context_class) {
303 (*env)->GetMethodID (env, context_class, "getCacheDir",
306 (*env)->GetMethodID (env, context_class, "getFilesDir",
308 if (!get_cache_dir_id || !get_files_dir_id) {
309 (*env)->DeleteLocalRef (env, context_class);
313 file_class = (*env)->FindClass (env, "java/io/File");
315 (*env)->DeleteLocalRef (env, context_class);
318 get_absolute_path_id =
319 (*env)->GetMethodID (env, file_class, "getAbsolutePath",
320 "()Ljava/lang/String;");
321 if (!get_absolute_path_id) {
322 (*env)->DeleteLocalRef (env, context_class);
323 (*env)->DeleteLocalRef (env, file_class);
327 dir = (*env)->CallObjectMethod (env, context, get_cache_dir_id);
328 if ((*env)->ExceptionCheck (env)) {
329 (*env)->ExceptionDescribe (env);
330 (*env)->ExceptionClear (env);
331 (*env)->DeleteLocalRef (env, context_class);
332 (*env)->DeleteLocalRef (env, file_class);
337 abs_path = (*env)->CallObjectMethod (env, dir, get_absolute_path_id);
338 if ((*env)->ExceptionCheck (env)) {
339 (*env)->ExceptionDescribe (env);
340 (*env)->ExceptionClear (env);
341 (*env)->DeleteLocalRef (env, dir);
342 (*env)->DeleteLocalRef (env, context_class);
343 (*env)->DeleteLocalRef (env, file_class);
346 abs_path_str = (*env)->GetStringUTFChars (env, abs_path, NULL);
347 if ((*env)->ExceptionCheck (env)) {
348 (*env)->ExceptionDescribe (env);
349 (*env)->ExceptionClear (env);
350 (*env)->DeleteLocalRef (env, abs_path);
351 (*env)->DeleteLocalRef (env, dir);
352 (*env)->DeleteLocalRef (env, context_class);
353 (*env)->DeleteLocalRef (env, file_class);
356 *cache_dir = abs_path ? g_strdup (abs_path_str) : NULL;
358 (*env)->ReleaseStringUTFChars (env, abs_path, abs_path_str);
359 (*env)->DeleteLocalRef (env, abs_path);
360 (*env)->DeleteLocalRef (env, dir);
363 dir = (*env)->CallObjectMethod (env, context, get_files_dir_id);
364 if ((*env)->ExceptionCheck (env)) {
365 (*env)->ExceptionDescribe (env);
366 (*env)->ExceptionClear (env);
367 (*env)->DeleteLocalRef (env, context_class);
368 (*env)->DeleteLocalRef (env, file_class);
372 abs_path = (*env)->CallObjectMethod (env, dir, get_absolute_path_id);
373 if ((*env)->ExceptionCheck (env)) {
374 (*env)->ExceptionDescribe (env);
375 (*env)->ExceptionClear (env);
376 (*env)->DeleteLocalRef (env, dir);
377 (*env)->DeleteLocalRef (env, context_class);
378 (*env)->DeleteLocalRef (env, file_class);
381 abs_path_str = (*env)->GetStringUTFChars (env, abs_path, NULL);
382 if ((*env)->ExceptionCheck (env)) {
383 (*env)->ExceptionDescribe (env);
384 (*env)->ExceptionClear (env);
385 (*env)->DeleteLocalRef (env, abs_path);
386 (*env)->DeleteLocalRef (env, dir);
387 (*env)->DeleteLocalRef (env, context_class);
388 (*env)->DeleteLocalRef (env, file_class);
391 *files_dir = files_dir ? g_strdup (abs_path_str) : NULL;
393 (*env)->ReleaseStringUTFChars (env, abs_path, abs_path_str);
394 (*env)->DeleteLocalRef (env, abs_path);
395 (*env)->DeleteLocalRef (env, dir);
398 (*env)->DeleteLocalRef (env, file_class);
399 (*env)->DeleteLocalRef (env, context_class);
405 gst_android_get_application_context (void)
411 gst_android_get_application_class_loader (void)
413 return _class_loader;
417 gst_android_get_java_vm (void)
423 init (JNIEnv * env, jobject context)
425 jclass context_cls = NULL;
426 jmethodID get_class_loader_id = 0;
428 jobject class_loader = NULL;
430 context_cls = (*env)->GetObjectClass (env, context);
435 get_class_loader_id = (*env)->GetMethodID (env, context_cls,
436 "getClassLoader", "()Ljava/lang/ClassLoader;");
437 if ((*env)->ExceptionCheck (env)) {
438 (*env)->ExceptionDescribe (env);
439 (*env)->ExceptionClear (env);
443 class_loader = (*env)->CallObjectMethod (env, context, get_class_loader_id);
444 if ((*env)->ExceptionCheck (env)) {
445 (*env)->ExceptionDescribe (env);
446 (*env)->ExceptionClear (env);
451 (*env)->DeleteGlobalRef (env, _context);
453 _context = (*env)->NewGlobalRef (env, context);
456 (*env)->DeleteGlobalRef (env, _class_loader);
458 _class_loader = (*env)->NewGlobalRef (env, class_loader);
464 gst_android_init (JNIEnv * env, jobject context)
469 GError *error = NULL;
471 if (!init (env, context)) {
472 __android_log_print (ANDROID_LOG_INFO, "GStreamer",
473 "GStreamer failed to initialize");
476 if (gst_is_initialized ()) {
477 __android_log_print (ANDROID_LOG_INFO, "GStreamer",
478 "GStreamer already initialized");
482 if (!get_application_dirs (env, context, &cache_dir, &files_dir)) {
483 __android_log_print (ANDROID_LOG_ERROR, "GStreamer",
484 "Failed to get application dirs");
488 g_setenv ("TMP", cache_dir, TRUE);
489 g_setenv ("TEMP", cache_dir, TRUE);
490 g_setenv ("TMPDIR", cache_dir, TRUE);
491 g_setenv ("XDG_RUNTIME_DIR", cache_dir, TRUE);
492 g_setenv ("XDG_CACHE_HOME", cache_dir, TRUE);
493 registry = g_build_filename (cache_dir, "registry.bin", NULL);
494 g_setenv ("GST_REGISTRY", registry, TRUE);
496 g_setenv ("GST_REGISTRY_REUSE_PLUGIN_SCANNER", "no", TRUE);
497 /* TODO: Should probably also set GST_PLUGIN_SCANNER and GST_PLUGIN_SYSTEM_PATH */
500 gchar *fontconfig, *certs;
502 g_setenv ("HOME", files_dir, TRUE);
503 g_setenv ("XDG_DATA_DIRS", files_dir, TRUE);
504 g_setenv ("XDG_CONFIG_DIRS", files_dir, TRUE);
505 g_setenv ("XDG_CONFIG_HOME", files_dir, TRUE);
506 g_setenv ("XDG_DATA_HOME", files_dir, TRUE);
508 fontconfig = g_build_filename (files_dir, "fontconfig", NULL);
509 g_setenv ("FONTCONFIG_PATH", fontconfig, TRUE);
513 g_build_filename (files_dir, "ssl", "certs", "ca-certificates.crt",
515 g_setenv ("CA_CERTIFICATES", certs, TRUE);
521 /* Set GLib print handlers */
522 g_set_print_handler (glib_print_handler);
523 g_set_printerr_handler (glib_printerr_handler);
524 g_log_set_default_handler (glib_log_handler, NULL);
526 /* Set GStreamer log handlers */
527 gst_debug_remove_log_function (NULL);
528 gst_debug_set_default_threshold (GST_LEVEL_WARNING);
529 gst_debug_add_log_function ((GstLogFunction) gst_debug_logcat, NULL, NULL);
531 /* get time we started for debugging messages */
532 _priv_gst_info_start_time = gst_util_get_timestamp ();
534 if (!gst_init_check (NULL, NULL, &error)) {
535 gchar *message = g_strdup_printf ("GStreamer initialization failed: %s",
536 error && error->message ? error->message : "(no message)");
537 jclass exception_class = (*env)->FindClass (env, "java/lang/Exception");
538 __android_log_print (ANDROID_LOG_ERROR, "GStreamer", "%s", message);
539 (*env)->ThrowNew (env, exception_class, message);
543 __android_log_print (ANDROID_LOG_INFO, "GStreamer",
544 "GStreamer initialization complete");
548 gst_android_init_jni (JNIEnv * env, jobject gstreamer, jobject context)
550 gst_android_init (env, context);
553 static JNINativeMethod native_methods[] = {
554 {"nativeInit", "(Landroid/content/Context;)V", (void *) gst_android_init_jni}
558 JNI_OnLoad (JavaVM * vm, void *reserved)
562 if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
563 __android_log_print (ANDROID_LOG_ERROR, "GStreamer",
564 "Could not retrieve JNIEnv");
567 jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/GStreamer");
569 __android_log_print (ANDROID_LOG_ERROR, "GStreamer",
570 "Could not retrieve class org.freedesktop.gstreamer.GStreamer. "
571 "Please copy GStreamer.java file into your project: "
572 "https://gitlab.freedesktop.org/gstreamer/gstreamer/-/tree/master/data/android/GStreamer.java");
575 if ((*env)->RegisterNatives (env, klass, native_methods,
576 G_N_ELEMENTS (native_methods))) {
577 __android_log_print (ANDROID_LOG_ERROR, "GStreamer",
578 "Could not register native methods for org.freedesktop.gstreamer.GStreamer");
582 /* Remember Java VM */
585 return JNI_VERSION_1_4;
589 JNI_OnUnload (JavaVM * vm, void *reversed)
593 if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
594 __android_log_print (ANDROID_LOG_ERROR, "GStreamer",
595 "Could not retrieve JNIEnv");
600 (*env)->DeleteGlobalRef (env, _context);
605 (*env)->DeleteGlobalRef (env, _class_loader);
606 _class_loader = NULL;