mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-25 08:38:21 +00:00
610 lines
17 KiB
C
610 lines
17 KiB
C
/* 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 <jni.h>
|
|
#include <android/log.h>
|
|
#include <string.h>
|
|
|
|
/* 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;
|
|
}
|