2 * JNI utility functions
4 * Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com>
6 * This file is part of FFmpeg.
8 * FFmpeg is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * FFmpeg is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with FFmpeg; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27 #include "libavutil/bprint.h"
28 #include "libavutil/log.h"
34 static JavaVM *java_vm = NULL;
35 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
37 JNIEnv *ff_jni_attach_env(int *attached, void *log_ctx)
44 pthread_mutex_lock(&lock);
45 if (java_vm == NULL) {
46 java_vm = av_jni_get_java_vm(log_ctx);
48 pthread_mutex_unlock(&lock);
51 av_log(log_ctx, AV_LOG_ERROR, "No Java virtual machine has been registered\n");
55 ret = (*java_vm)->GetEnv(java_vm, (void **)&env, JNI_VERSION_1_6);
58 if ((*java_vm)->AttachCurrentThread(java_vm, &env, NULL) != 0) {
59 av_log(log_ctx, AV_LOG_ERROR, "Failed to attach the JNI environment to the current thread\n");
68 av_log(log_ctx, AV_LOG_ERROR, "The specified JNI version is not supported\n");
71 av_log(log_ctx, AV_LOG_ERROR, "Failed to get the JNI environment attached to this thread");
78 int ff_jni_detach_env(void *log_ctx)
80 if (java_vm == NULL) {
81 av_log(log_ctx, AV_LOG_ERROR, "No Java virtual machine has been registered\n");
82 return AVERROR(EINVAL);
85 return (*java_vm)->DetachCurrentThread(java_vm);
88 char *ff_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, void *log_ctx)
91 const char *utf_chars = NULL;
99 utf_chars = (*env)->GetStringUTFChars(env, string, ©);
100 if ((*env)->ExceptionCheck(env)) {
101 (*env)->ExceptionClear(env);
102 av_log(log_ctx, AV_LOG_ERROR, "String.getStringUTFChars() threw an exception\n");
106 ret = av_strdup(utf_chars);
108 (*env)->ReleaseStringUTFChars(env, string, utf_chars);
109 if ((*env)->ExceptionCheck(env)) {
110 (*env)->ExceptionClear(env);
111 av_log(log_ctx, AV_LOG_ERROR, "String.releaseStringUTFChars() threw an exception\n");
118 jstring ff_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, void *log_ctx)
122 ret = (*env)->NewStringUTF(env, utf_chars);
123 if ((*env)->ExceptionCheck(env)) {
124 (*env)->ExceptionClear(env);
125 av_log(log_ctx, AV_LOG_ERROR, "NewStringUTF() threw an exception\n");
132 int ff_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, void *log_ctx)
139 char *message = NULL;
141 jclass class_class = NULL;
142 jmethodID get_name_id = NULL;
144 jclass exception_class = NULL;
145 jmethodID get_message_id = NULL;
147 jstring string = NULL;
149 av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
151 exception_class = (*env)->GetObjectClass(env, exception);
152 if ((*env)->ExceptionCheck(env)) {
153 (*env)->ExceptionClear(env);
154 av_log(log_ctx, AV_LOG_ERROR, "Could not find Throwable class\n");
155 ret = AVERROR_EXTERNAL;
159 class_class = (*env)->GetObjectClass(env, exception_class);
160 if ((*env)->ExceptionCheck(env)) {
161 (*env)->ExceptionClear(env);
162 av_log(log_ctx, AV_LOG_ERROR, "Could not find Throwable class's class\n");
163 ret = AVERROR_EXTERNAL;
167 get_name_id = (*env)->GetMethodID(env, class_class, "getName", "()Ljava/lang/String;");
168 if ((*env)->ExceptionCheck(env)) {
169 (*env)->ExceptionClear(env);
170 av_log(log_ctx, AV_LOG_ERROR, "Could not find method Class.getName()\n");
171 ret = AVERROR_EXTERNAL;
175 string = (*env)->CallObjectMethod(env, exception_class, get_name_id);
176 if ((*env)->ExceptionCheck(env)) {
177 (*env)->ExceptionClear(env);
178 av_log(log_ctx, AV_LOG_ERROR, "Class.getName() threw an exception\n");
179 ret = AVERROR_EXTERNAL;
184 name = ff_jni_jstring_to_utf_chars(env, string, log_ctx);
185 (*env)->DeleteLocalRef(env, string);
189 get_message_id = (*env)->GetMethodID(env, exception_class, "getMessage", "()Ljava/lang/String;");
190 if ((*env)->ExceptionCheck(env)) {
191 (*env)->ExceptionClear(env);
192 av_log(log_ctx, AV_LOG_ERROR, "Could not find method java/lang/Throwable.getMessage()\n");
193 ret = AVERROR_EXTERNAL;
197 string = (*env)->CallObjectMethod(env, exception, get_message_id);
198 if ((*env)->ExceptionCheck(env)) {
199 (*env)->ExceptionClear(env);
200 av_log(log_ctx, AV_LOG_ERROR, "Throwable.getMessage() threw an exception\n");
201 ret = AVERROR_EXTERNAL;
206 message = ff_jni_jstring_to_utf_chars(env, string, log_ctx);
207 (*env)->DeleteLocalRef(env, string);
211 if (name && message) {
212 av_bprintf(&bp, "%s: %s", name, message);
213 } else if (name && !message) {
214 av_bprintf(&bp, "%s occurred", name);
215 } else if (!name && message) {
216 av_bprintf(&bp, "Exception: %s", message);
218 av_log(log_ctx, AV_LOG_WARNING, "Could not retreive exception name and message\n");
219 av_bprintf(&bp, "Exception occurred");
222 ret = av_bprint_finalize(&bp, error);
229 (*env)->DeleteLocalRef(env, class_class);
232 if (exception_class) {
233 (*env)->DeleteLocalRef(env, exception_class);
237 (*env)->DeleteLocalRef(env, string);
243 int ff_jni_exception_check(JNIEnv *env, int log, void *log_ctx)
247 jthrowable exception;
249 char *message = NULL;
251 if (!(*(env))->ExceptionCheck((env))) {
256 (*(env))->ExceptionClear((env));
260 exception = (*env)->ExceptionOccurred(env);
261 (*(env))->ExceptionClear((env));
263 if ((ret = ff_jni_exception_get_summary(env, exception, &message, log_ctx)) < 0) {
264 (*env)->DeleteLocalRef(env, exception);
268 (*env)->DeleteLocalRef(env, exception);
270 av_log(log_ctx, AV_LOG_ERROR, "%s\n", message);
276 int ff_jni_init_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
279 jclass last_clazz = NULL;
281 for (i = 0; jfields_mapping[i].name; i++) {
282 int mandatory = jfields_mapping[i].mandatory;
283 enum FFJniFieldType type = jfields_mapping[i].type;
285 if (type == FF_JNI_CLASS) {
290 clazz = (*env)->FindClass(env, jfields_mapping[i].name);
291 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
295 last_clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) =
296 global ? (*env)->NewGlobalRef(env, clazz) : clazz;
300 ret = AVERROR_EXTERNAL;
306 jfieldID field_id = (*env)->GetFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
307 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
311 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
314 case FF_JNI_STATIC_FIELD: {
315 jfieldID field_id = (*env)->GetStaticFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
316 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
320 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
323 case FF_JNI_METHOD: {
324 jmethodID method_id = (*env)->GetMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
325 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
329 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
332 case FF_JNI_STATIC_METHOD: {
333 jmethodID method_id = (*env)->GetStaticMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
334 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
338 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
342 av_log(log_ctx, AV_LOG_ERROR, "Unknown JNI field type\n");
343 ret = AVERROR(EINVAL);
351 /* reset jfields in case of failure so it does not leak references */
352 ff_jni_reset_jfields(env, jfields, jfields_mapping, global, log_ctx);
358 int ff_jni_reset_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
362 for (i = 0; jfields_mapping[i].name; i++) {
363 enum FFJniFieldType type = jfields_mapping[i].type;
367 jclass clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset);
372 (*env)->DeleteGlobalRef(env, clazz);
374 (*env)->DeleteLocalRef(env, clazz);
377 *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
381 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
384 case FF_JNI_STATIC_FIELD: {
385 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
388 case FF_JNI_METHOD: {
389 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
392 case FF_JNI_STATIC_METHOD: {
393 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
397 av_log(log_ctx, AV_LOG_ERROR, "Unknown JNI field type\n");