From fa38827c44ec369fed2b1551a653b8fc188cedfc Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Fri, 4 Sep 2020 20:52:47 -0400 Subject: [PATCH] Android: Implement JNI_OnLoad() When building for Android, chances are that gstreamer is going to be loaded from Java using System.loadLibrary(). In that case we can initialize GStreamer (including static plugins), redirect log functions, etc. This code is copied from cerbero because it can be used with gstreamer-full-1.0 too. Cerbero needs to be adapted to drop that code and generate gst_init_static_plugins() function. Part-of: --- .../gstreamer/data/android/GStreamer.java | 105 +++ subprojects/gstreamer/data/meson.build | 4 + subprojects/gstreamer/gst/gstandroid.c | 610 ++++++++++++++++++ subprojects/gstreamer/gst/meson.build | 10 +- 4 files changed, 727 insertions(+), 2 deletions(-) create mode 100644 subprojects/gstreamer/data/android/GStreamer.java create mode 100644 subprojects/gstreamer/gst/gstandroid.c diff --git a/subprojects/gstreamer/data/android/GStreamer.java b/subprojects/gstreamer/data/android/GStreamer.java new file mode 100644 index 0000000000..066b2152b4 --- /dev/null +++ b/subprojects/gstreamer/data/android/GStreamer.java @@ -0,0 +1,105 @@ +/** + * Copy this file into your Android project and call init(). If your project + * contains fonts and/or certificates in assets, uncomment copyFonts() and/or + * copyCaCertificates() lines in init(). + */ +package org.freedesktop.gstreamer; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import android.content.Context; +import android.content.res.AssetManager; +import android.system.Os; + +public class GStreamer { + private static native void nativeInit(Context context) throws Exception; + + public static void init(Context context) throws Exception { + //copyFonts(context); + //copyCaCertificates(context); + nativeInit(context); + } + + private static void copyFonts(Context context) { + AssetManager assetManager = context.getAssets(); + File filesDir = context.getFilesDir(); + File fontsFCDir = new File (filesDir, "fontconfig"); + File fontsDir = new File (fontsFCDir, "fonts"); + File fontsCfg = new File (fontsFCDir, "fonts.conf"); + + fontsDir.mkdirs(); + + try { + /* Copy the config file */ + copyFile (assetManager, "fontconfig/fonts.conf", fontsCfg); + /* Copy the fonts */ + for(String filename : assetManager.list("fontconfig/fonts/truetype")) { + File font = new File(fontsDir, filename); + copyFile (assetManager, "fontconfig/fonts/truetype/" + filename, font); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void copyCaCertificates(Context context) { + AssetManager assetManager = context.getAssets(); + File filesDir = context.getFilesDir(); + File sslDir = new File (filesDir, "ssl"); + File certsDir = new File (sslDir, "certs"); + File certs = new File (certsDir, "ca-certificates.crt"); + + certsDir.mkdirs(); + + try { + /* Copy the certificates file */ + copyFile (assetManager, "ssl/certs/ca-certificates.crt", certs); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void copyFile(AssetManager assetManager, String assetPath, File outFile) throws IOException { + InputStream in = null; + OutputStream out = null; + IOException exception = null; + + if (outFile.exists()) + outFile.delete(); + + try { + in = assetManager.open(assetPath); + out = new FileOutputStream(outFile); + + byte[] buffer = new byte[1024]; + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + out.flush(); + } catch (IOException e) { + exception = e; + } finally { + if (in != null) + try { + in.close(); + } catch (IOException e) { + if (exception == null) + exception = e; + } + if (out != null) + try { + out.close(); + } catch (IOException e) { + if (exception == null) + exception = e; + } + if (exception != null) + throw exception; + } + } +} diff --git a/subprojects/gstreamer/data/meson.build b/subprojects/gstreamer/data/meson.build index 25f2643f09..aafcb4f350 100644 --- a/subprojects/gstreamer/data/meson.build +++ b/subprojects/gstreamer/data/meson.build @@ -7,3 +7,7 @@ if (bashcomp_found) install_dir : bash_completions_dir, install_tag : 'runtime') endif + +if host_system == 'android' + install_data('android/GStreamer.java', install_dir: get_option('datadir') / 'gst-android/ndk-build') +endif diff --git a/subprojects/gstreamer/gst/gstandroid.c b/subprojects/gstreamer/gst/gstandroid.c new file mode 100644 index 0000000000..4212a544b9 --- /dev/null +++ b/subprojects/gstreamer/gst/gstandroid.c @@ -0,0 +1,610 @@ +/* This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gst_private.h" +#include "gst.h" + +#include +#include +#include + +/* AMC plugin uses g_module_symbol() to find those symbols */ +GST_API jobject gst_android_get_application_context (void); +GST_API jobject gst_android_get_application_class_loader (void); +GST_API JavaVM *gst_android_get_java_vm (void); + +/* XXX: Workaround for Android <21 making signal() an inline function + * around bsd_signal(), and Android >= 21 not having any bsd_signal() + * symbol but only signal(). + * See https://bugzilla.gnome.org/show_bug.cgi?id=766235 + */ +static gpointer +load_real_signal (gpointer data) +{ + GModule *module; + gpointer ret = NULL; + + module = g_module_open ("libc.so", G_MODULE_BIND_LOCAL); + g_module_symbol (module, "signal", &ret); + + /* As fallback, let's try bsd_signal */ + if (ret == NULL) { + g_warning ("Can't find signal(3) in libc.so!"); + g_module_symbol (module, "bsd_signal", &ret); + } + + g_module_close (module); + + return ret; +} + +__sighandler_t bsd_signal (int signum, __sighandler_t handler) + __attribute__ ((weak)); +__sighandler_t +bsd_signal (int signum, __sighandler_t handler) +{ + static GOnce gonce = G_ONCE_INIT; + __sighandler_t (*real_signal) (int signum, __sighandler_t handler); + + g_once (&gonce, load_real_signal, NULL); + + real_signal = gonce.retval; + g_assert (real_signal != NULL); + + return real_signal (signum, handler); +} + +static jobject _context = NULL; +static jobject _class_loader = NULL; +static JavaVM *_java_vm = NULL; +static GstClockTime _priv_gst_info_start_time; + +static void +glib_print_handler (const gchar * string) +{ + __android_log_print (ANDROID_LOG_INFO, "GLib+stdout", "%s", string); +} + +static void +glib_printerr_handler (const gchar * string) +{ + __android_log_print (ANDROID_LOG_ERROR, "GLib+stderr", "%s", string); +} + + +/* Based on GLib's default handler */ +#define CHAR_IS_SAFE(wc) (!((wc < 0x20 && wc != '\t' && wc != '\n' && wc != '\r') || \ + (wc == 0x7f) || \ + (wc >= 0x80 && wc < 0xa0))) +#define FORMAT_UNSIGNED_BUFSIZE ((GLIB_SIZEOF_LONG * 3) + 3) +#define STRING_BUFFER_SIZE (FORMAT_UNSIGNED_BUFSIZE + 32) +#define ALERT_LEVELS (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING) +#define DEFAULT_LEVELS (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE) +#define INFO_LEVELS (G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG) + +static void +escape_string (GString * string) +{ + const char *p = string->str; + gunichar wc; + + while (p < string->str + string->len) { + gboolean safe; + + wc = g_utf8_get_char_validated (p, -1); + if (wc == (gunichar) - 1 || wc == (gunichar) - 2) { + gchar *tmp; + guint pos; + + pos = p - string->str; + + /* Emit invalid UTF-8 as hex escapes + */ + tmp = g_strdup_printf ("\\x%02x", (guint) (guchar) * p); + g_string_erase (string, pos, 1); + g_string_insert (string, pos, tmp); + + p = string->str + (pos + 4); /* Skip over escape sequence */ + + g_free (tmp); + continue; + } + if (wc == '\r') { + safe = *(p + 1) == '\n'; + } else { + safe = CHAR_IS_SAFE (wc); + } + + if (!safe) { + gchar *tmp; + guint pos; + + pos = p - string->str; + + /* Largest char we escape is 0x0a, so we don't have to worry + * about 8-digit \Uxxxxyyyy + */ + tmp = g_strdup_printf ("\\u%04x", wc); + g_string_erase (string, pos, g_utf8_next_char (p) - p); + g_string_insert (string, pos, tmp); + g_free (tmp); + + p = string->str + (pos + 6); /* Skip over escape sequence */ + } else + p = g_utf8_next_char (p); + } +} + +static void +glib_log_handler (const gchar * log_domain, GLogLevelFlags log_level, + const gchar * message, gpointer user_data) +{ + gchar *string; + GString *gstring; + const gchar *domains; + gint android_log_level; + gchar *tag; + + if ((log_level & DEFAULT_LEVELS) || (log_level >> G_LOG_LEVEL_USER_SHIFT)) + goto emit; + + domains = g_getenv ("G_MESSAGES_DEBUG"); + if (((log_level & INFO_LEVELS) == 0) || + domains == NULL || + (strcmp (domains, "all") != 0 && (!log_domain + || !strstr (domains, log_domain)))) + return; + +emit: + + if (log_domain) + tag = g_strdup_printf ("GLib+%s", log_domain); + else + tag = g_strdup ("GLib"); + + switch (log_level & G_LOG_LEVEL_MASK) { + case G_LOG_LEVEL_ERROR: + case G_LOG_LEVEL_CRITICAL: + android_log_level = ANDROID_LOG_ERROR; + break; + case G_LOG_LEVEL_WARNING: + android_log_level = ANDROID_LOG_WARN; + break; + case G_LOG_LEVEL_MESSAGE: + case G_LOG_LEVEL_INFO: + android_log_level = ANDROID_LOG_INFO; + break; + case G_LOG_LEVEL_DEBUG: + android_log_level = ANDROID_LOG_DEBUG; + break; + default: + android_log_level = ANDROID_LOG_VERBOSE; + break; + } + + gstring = g_string_new (NULL); + if (!message) { + g_string_append (gstring, "(NULL) message"); + } else { + GString *msg = g_string_new (message); + escape_string (msg); + g_string_append (gstring, msg->str); + g_string_free (msg, TRUE); + } + string = g_string_free (gstring, FALSE); + + __android_log_print (android_log_level, tag, "%s", string); + + g_free (string); + g_free (tag); +} + +static void +gst_debug_logcat (GstDebugCategory * category, GstDebugLevel level, + const gchar * file, const gchar * function, gint line, + GObject * object, GstDebugMessage * message, gpointer unused) +{ + GstClockTime elapsed; + gint android_log_level; + gchar *tag; + + if (level > gst_debug_category_get_threshold (category)) + return; + + elapsed = GST_CLOCK_DIFF (_priv_gst_info_start_time, + gst_util_get_timestamp ()); + + switch (level) { + case GST_LEVEL_ERROR: + android_log_level = ANDROID_LOG_ERROR; + break; + case GST_LEVEL_WARNING: + android_log_level = ANDROID_LOG_WARN; + break; + case GST_LEVEL_FIXME: + case GST_LEVEL_INFO: + android_log_level = ANDROID_LOG_INFO; + break; + case GST_LEVEL_DEBUG: + android_log_level = ANDROID_LOG_DEBUG; + break; + default: + android_log_level = ANDROID_LOG_VERBOSE; + break; + } + + tag = g_strdup_printf ("GStreamer+%s", + gst_debug_category_get_name (category)); + + if (object) { + gchar *obj; + + if (GST_IS_PAD (object) && GST_OBJECT_NAME (object)) { + obj = g_strdup_printf ("<%s:%s>", GST_DEBUG_PAD_NAME (object)); + } else if (GST_IS_OBJECT (object) && GST_OBJECT_NAME (object)) { + obj = g_strdup_printf ("<%s>", GST_OBJECT_NAME (object)); + } else if (G_IS_OBJECT (object)) { + obj = g_strdup_printf ("<%s@%p>", G_OBJECT_TYPE_NAME (object), object); + } else { + obj = g_strdup_printf ("<%p>", object); + } + + __android_log_print (android_log_level, tag, + "%" GST_TIME_FORMAT " %p %s:%d:%s:%s %s\n", + GST_TIME_ARGS (elapsed), g_thread_self (), + file, line, function, obj, gst_debug_message_get (message)); + + g_free (obj); + } else { + __android_log_print (android_log_level, tag, + "%" GST_TIME_FORMAT " %p %s:%d:%s %s\n", + GST_TIME_ARGS (elapsed), g_thread_self (), + file, line, function, gst_debug_message_get (message)); + } + g_free (tag); +} + +static gboolean +get_application_dirs (JNIEnv * env, jobject context, gchar ** cache_dir, + gchar ** files_dir) +{ + jclass context_class; + jmethodID get_cache_dir_id, get_files_dir_id; + jclass file_class; + jmethodID get_absolute_path_id; + jobject dir; + jstring abs_path; + const gchar *abs_path_str; + + *cache_dir = *files_dir = NULL; + + context_class = (*env)->GetObjectClass (env, context); + if (!context_class) { + return FALSE; + } + get_cache_dir_id = + (*env)->GetMethodID (env, context_class, "getCacheDir", + "()Ljava/io/File;"); + get_files_dir_id = + (*env)->GetMethodID (env, context_class, "getFilesDir", + "()Ljava/io/File;"); + if (!get_cache_dir_id || !get_files_dir_id) { + (*env)->DeleteLocalRef (env, context_class); + return FALSE; + } + + file_class = (*env)->FindClass (env, "java/io/File"); + if (!file_class) { + (*env)->DeleteLocalRef (env, context_class); + return FALSE; + } + get_absolute_path_id = + (*env)->GetMethodID (env, file_class, "getAbsolutePath", + "()Ljava/lang/String;"); + if (!get_absolute_path_id) { + (*env)->DeleteLocalRef (env, context_class); + (*env)->DeleteLocalRef (env, file_class); + return FALSE; + } + + dir = (*env)->CallObjectMethod (env, context, get_cache_dir_id); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + (*env)->DeleteLocalRef (env, context_class); + (*env)->DeleteLocalRef (env, file_class); + return FALSE; + } + + if (dir) { + abs_path = (*env)->CallObjectMethod (env, dir, get_absolute_path_id); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + (*env)->DeleteLocalRef (env, dir); + (*env)->DeleteLocalRef (env, context_class); + (*env)->DeleteLocalRef (env, file_class); + return FALSE; + } + abs_path_str = (*env)->GetStringUTFChars (env, abs_path, NULL); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + (*env)->DeleteLocalRef (env, abs_path); + (*env)->DeleteLocalRef (env, dir); + (*env)->DeleteLocalRef (env, context_class); + (*env)->DeleteLocalRef (env, file_class); + return FALSE; + } + *cache_dir = abs_path ? g_strdup (abs_path_str) : NULL; + + (*env)->ReleaseStringUTFChars (env, abs_path, abs_path_str); + (*env)->DeleteLocalRef (env, abs_path); + (*env)->DeleteLocalRef (env, dir); + } + + dir = (*env)->CallObjectMethod (env, context, get_files_dir_id); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + (*env)->DeleteLocalRef (env, context_class); + (*env)->DeleteLocalRef (env, file_class); + return FALSE; + } + if (dir) { + abs_path = (*env)->CallObjectMethod (env, dir, get_absolute_path_id); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + (*env)->DeleteLocalRef (env, dir); + (*env)->DeleteLocalRef (env, context_class); + (*env)->DeleteLocalRef (env, file_class); + return FALSE; + } + abs_path_str = (*env)->GetStringUTFChars (env, abs_path, NULL); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + (*env)->DeleteLocalRef (env, abs_path); + (*env)->DeleteLocalRef (env, dir); + (*env)->DeleteLocalRef (env, context_class); + (*env)->DeleteLocalRef (env, file_class); + return FALSE; + } + *files_dir = files_dir ? g_strdup (abs_path_str) : NULL; + + (*env)->ReleaseStringUTFChars (env, abs_path, abs_path_str); + (*env)->DeleteLocalRef (env, abs_path); + (*env)->DeleteLocalRef (env, dir); + } + + (*env)->DeleteLocalRef (env, file_class); + (*env)->DeleteLocalRef (env, context_class); + + return TRUE; +} + +jobject +gst_android_get_application_context (void) +{ + return _context; +} + +jobject +gst_android_get_application_class_loader (void) +{ + return _class_loader; +} + +JavaVM * +gst_android_get_java_vm (void) +{ + return _java_vm; +} + +static gboolean +init (JNIEnv * env, jobject context) +{ + jclass context_cls = NULL; + jmethodID get_class_loader_id = 0; + + jobject class_loader = NULL; + + context_cls = (*env)->GetObjectClass (env, context); + if (!context_cls) { + return FALSE; + } + + get_class_loader_id = (*env)->GetMethodID (env, context_cls, + "getClassLoader", "()Ljava/lang/ClassLoader;"); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + return FALSE; + } + + class_loader = (*env)->CallObjectMethod (env, context, get_class_loader_id); + if ((*env)->ExceptionCheck (env)) { + (*env)->ExceptionDescribe (env); + (*env)->ExceptionClear (env); + return FALSE; + } + + if (_context) { + (*env)->DeleteGlobalRef (env, _context); + } + _context = (*env)->NewGlobalRef (env, context); + + if (_class_loader) { + (*env)->DeleteGlobalRef (env, _class_loader); + } + _class_loader = (*env)->NewGlobalRef (env, class_loader); + + return TRUE; +} + +static void +gst_android_init (JNIEnv * env, jobject context) +{ + gchar *cache_dir; + gchar *files_dir; + gchar *registry; + GError *error = NULL; + + if (!init (env, context)) { + __android_log_print (ANDROID_LOG_INFO, "GStreamer", + "GStreamer failed to initialize"); + } + + if (gst_is_initialized ()) { + __android_log_print (ANDROID_LOG_INFO, "GStreamer", + "GStreamer already initialized"); + return; + } + + if (!get_application_dirs (env, context, &cache_dir, &files_dir)) { + __android_log_print (ANDROID_LOG_ERROR, "GStreamer", + "Failed to get application dirs"); + } + + if (cache_dir) { + g_setenv ("TMP", cache_dir, TRUE); + g_setenv ("TEMP", cache_dir, TRUE); + g_setenv ("TMPDIR", cache_dir, TRUE); + g_setenv ("XDG_RUNTIME_DIR", cache_dir, TRUE); + g_setenv ("XDG_CACHE_HOME", cache_dir, TRUE); + registry = g_build_filename (cache_dir, "registry.bin", NULL); + g_setenv ("GST_REGISTRY", registry, TRUE); + g_free (registry); + g_setenv ("GST_REGISTRY_REUSE_PLUGIN_SCANNER", "no", TRUE); + /* TODO: Should probably also set GST_PLUGIN_SCANNER and GST_PLUGIN_SYSTEM_PATH */ + } + if (files_dir) { + gchar *fontconfig, *certs; + + g_setenv ("HOME", files_dir, TRUE); + g_setenv ("XDG_DATA_DIRS", files_dir, TRUE); + g_setenv ("XDG_CONFIG_DIRS", files_dir, TRUE); + g_setenv ("XDG_CONFIG_HOME", files_dir, TRUE); + g_setenv ("XDG_DATA_HOME", files_dir, TRUE); + + fontconfig = g_build_filename (files_dir, "fontconfig", NULL); + g_setenv ("FONTCONFIG_PATH", fontconfig, TRUE); + g_free (fontconfig); + + certs = + g_build_filename (files_dir, "ssl", "certs", "ca-certificates.crt", + NULL); + g_setenv ("CA_CERTIFICATES", certs, TRUE); + g_free (certs); + } + g_free (cache_dir); + g_free (files_dir); + + /* Set GLib print handlers */ + g_set_print_handler (glib_print_handler); + g_set_printerr_handler (glib_printerr_handler); + g_log_set_default_handler (glib_log_handler, NULL); + + /* Set GStreamer log handlers */ + gst_debug_remove_log_function (NULL); + gst_debug_set_default_threshold (GST_LEVEL_WARNING); + gst_debug_add_log_function ((GstLogFunction) gst_debug_logcat, NULL, NULL); + + /* get time we started for debugging messages */ + _priv_gst_info_start_time = gst_util_get_timestamp (); + + if (!gst_init_check (NULL, NULL, &error)) { + gchar *message = g_strdup_printf ("GStreamer initialization failed: %s", + error && error->message ? error->message : "(no message)"); + jclass exception_class = (*env)->FindClass (env, "java/lang/Exception"); + __android_log_print (ANDROID_LOG_ERROR, "GStreamer", "%s", message); + (*env)->ThrowNew (env, exception_class, message); + g_free (message); + return; + } + __android_log_print (ANDROID_LOG_INFO, "GStreamer", + "GStreamer initialization complete"); +} + +static void +gst_android_init_jni (JNIEnv * env, jobject gstreamer, jobject context) +{ + gst_android_init (env, context); +} + +static JNINativeMethod native_methods[] = { + {"nativeInit", "(Landroid/content/Context;)V", (void *) gst_android_init_jni} +}; + +jint +JNI_OnLoad (JavaVM * vm, void *reserved) +{ + JNIEnv *env = NULL; + + if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) { + __android_log_print (ANDROID_LOG_ERROR, "GStreamer", + "Could not retrieve JNIEnv"); + return 0; + } + jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/GStreamer"); + if (!klass) { + __android_log_print (ANDROID_LOG_ERROR, "GStreamer", + "Could not retrieve class org.freedesktop.gstreamer.GStreamer. " + "Please copy GStreamer.java file into your project: " + "https://gitlab.freedesktop.org/gstreamer/gstreamer/-/tree/master/data/android/GStreamer.java"); + return 0; + } + if ((*env)->RegisterNatives (env, klass, native_methods, + G_N_ELEMENTS (native_methods))) { + __android_log_print (ANDROID_LOG_ERROR, "GStreamer", + "Could not register native methods for org.freedesktop.gstreamer.GStreamer"); + return 0; + } + + /* Remember Java VM */ + _java_vm = vm; + + return JNI_VERSION_1_4; +} + +void +JNI_OnUnload (JavaVM * vm, void *reversed) +{ + JNIEnv *env = NULL; + + if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) { + __android_log_print (ANDROID_LOG_ERROR, "GStreamer", + "Could not retrieve JNIEnv"); + return; + } + + if (_context) { + (*env)->DeleteGlobalRef (env, _context); + _context = NULL; + } + + if (_class_loader) { + (*env)->DeleteGlobalRef (env, _class_loader); + _class_loader = NULL; + } + + _java_vm = NULL; +} diff --git a/subprojects/gstreamer/gst/meson.build b/subprojects/gstreamer/gst/meson.build index 914e18d98a..2692e195fd 100644 --- a/subprojects/gstreamer/gst/meson.build +++ b/subprojects/gstreamer/gst/meson.build @@ -150,6 +150,12 @@ gst_headers = files( ) install_headers(gst_headers, subdir : 'gstreamer-1.0/gst') +extra_deps = [] +if host_system == 'android' + gst_sources += 'gstandroid.c' + extra_deps += cc.find_library('log') +endif + gst_registry = get_option('registry') if gst_registry gst_registry_sources = files('gstregistrybinary.c') @@ -246,8 +252,8 @@ libgst = library('gstreamer-1.0', gst_sources, include_directories('parse')], link_with : printf_lib, install : true, - dependencies : [gobject_dep, gmodule_dep, glib_dep, mathlib, dl_dep] + backtrace_deps - + platform_deps, + dependencies : [gobject_dep, gmodule_dep, glib_dep, mathlib, dl_dep, + backtrace_deps, platform_deps, extra_deps], ) pkg_name = 'gstreamer-1.0'