FFmpeg
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
ffjni.c
Go to the documentation of this file.
1 /*
2  * JNI utility functions
3  *
4  * Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com>
5  *
6  * This file is part of FFmpeg.
7  *
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.
12  *
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.
17  *
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
21  */
22 
23 #include <jni.h>
24 #include <pthread.h>
25 #include <stdlib.h>
26 
27 #include "libavutil/bprint.h"
28 #include "libavutil/log.h"
29 
30 #include "config.h"
31 #include "jni.h"
32 #include "ffjni.h"
33 
34 static JavaVM *java_vm = NULL;
35 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
36 
37 JNIEnv *ff_jni_attach_env(int *attached, void *log_ctx)
38 {
39  int ret = 0;
40  JNIEnv *env = NULL;
41 
42  *attached = 0;
43 
45  if (java_vm == NULL) {
46  java_vm = av_jni_get_java_vm(log_ctx);
47  }
49 
50  if (!java_vm) {
51  av_log(log_ctx, AV_LOG_ERROR, "No Java virtual machine has been registered\n");
52  return NULL;
53  }
54 
55  ret = (*java_vm)->GetEnv(java_vm, (void **)&env, JNI_VERSION_1_6);
56  switch(ret) {
57  case JNI_EDETACHED:
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");
60  env = NULL;
61  } else {
62  *attached = 1;
63  }
64  break;
65  case JNI_OK:
66  break;
67  case JNI_EVERSION:
68  av_log(log_ctx, AV_LOG_ERROR, "The specified JNI version is not supported\n");
69  break;
70  default:
71  av_log(log_ctx, AV_LOG_ERROR, "Failed to get the JNI environment attached to this thread");
72  break;
73  }
74 
75  return env;
76 }
77 
78 int ff_jni_detach_env(void *log_ctx)
79 {
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);
83  }
84 
85  return (*java_vm)->DetachCurrentThread(java_vm);
86 }
87 
88 char *ff_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, void *log_ctx)
89 {
90  char *ret = NULL;
91  const char *utf_chars = NULL;
92 
93  jboolean copy = 0;
94 
95  if (!string) {
96  return NULL;
97  }
98 
99  utf_chars = (*env)->GetStringUTFChars(env, string, &copy);
100  if ((*env)->ExceptionCheck(env)) {
101  (*env)->ExceptionClear(env);
102  av_log(log_ctx, AV_LOG_ERROR, "String.getStringUTFChars() threw an exception\n");
103  return NULL;
104  }
105 
106  ret = av_strdup(utf_chars);
107 
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");
112  return NULL;
113  }
114 
115  return ret;
116 }
117 
118 jstring ff_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, void *log_ctx)
119 {
120  jstring ret;
121 
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");
126  return NULL;
127  }
128 
129  return ret;
130 }
131 
132 int ff_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, void *log_ctx)
133 {
134  int ret = 0;
135 
136  AVBPrint bp;
137 
138  char *name = NULL;
139  char *message = NULL;
140 
141  jclass class_class = NULL;
142  jmethodID get_name_id = NULL;
143 
144  jclass exception_class = NULL;
145  jmethodID get_message_id = NULL;
146 
147  jstring string = NULL;
148 
150 
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;
156  goto done;
157  }
158 
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;
164  goto done;
165  }
166 
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;
172  goto done;
173  }
174 
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;
180  goto done;
181  }
182 
183  if (string) {
184  name = ff_jni_jstring_to_utf_chars(env, string, log_ctx);
185  (*env)->DeleteLocalRef(env, string);
186  string = NULL;
187  }
188 
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;
194  goto done;
195  }
196 
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;
202  goto done;
203  }
204 
205  if (string) {
206  message = ff_jni_jstring_to_utf_chars(env, string, log_ctx);
207  (*env)->DeleteLocalRef(env, string);
208  string = NULL;
209  }
210 
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);
217  } else {
218  av_log(log_ctx, AV_LOG_WARNING, "Could not retreive exception name and message\n");
219  av_bprintf(&bp, "Exception occurred");
220  }
221 
222  ret = av_bprint_finalize(&bp, error);
223 done:
224 
225  av_free(name);
226  av_free(message);
227 
228  if (class_class) {
229  (*env)->DeleteLocalRef(env, class_class);
230  }
231 
232  if (exception_class) {
233  (*env)->DeleteLocalRef(env, exception_class);
234  }
235 
236  if (string) {
237  (*env)->DeleteLocalRef(env, string);
238  }
239 
240  return ret;
241 }
242 
243 int ff_jni_exception_check(JNIEnv *env, int log, void *log_ctx)
244 {
245  int ret;
246 
247  jthrowable exception;
248 
249  char *message = NULL;
250 
251  if (!(*(env))->ExceptionCheck((env))) {
252  return 0;
253  }
254 
255  if (!log) {
256  (*(env))->ExceptionClear((env));
257  return -1;
258  }
259 
260  exception = (*env)->ExceptionOccurred(env);
261  (*(env))->ExceptionClear((env));
262 
263  if ((ret = ff_jni_exception_get_summary(env, exception, &message, log_ctx)) < 0) {
264  (*env)->DeleteLocalRef(env, exception);
265  return ret;
266  }
267 
268  (*env)->DeleteLocalRef(env, exception);
269 
270  av_log(log_ctx, AV_LOG_ERROR, "%s\n", message);
271  av_free(message);
272 
273  return -1;
274 }
275 
276 int ff_jni_init_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
277 {
278  int i, ret = 0;
279  jclass last_clazz = NULL;
280 
281  for (i = 0; jfields_mapping[i].name; i++) {
282  int mandatory = jfields_mapping[i].mandatory;
283  enum FFJniFieldType type = jfields_mapping[i].type;
284 
285  if (type == FF_JNI_CLASS) {
286  jclass clazz;
287 
288  last_clazz = NULL;
289 
290  clazz = (*env)->FindClass(env, jfields_mapping[i].name);
291  if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
292  goto done;
293  }
294 
295  last_clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) =
296  global ? (*env)->NewGlobalRef(env, clazz) : clazz;
297  } else {
298 
299  if (!last_clazz) {
300  ret = AVERROR_EXTERNAL;
301  break;
302  }
303 
304  switch(type) {
305  case FF_JNI_FIELD: {
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) {
308  goto done;
309  }
310 
311  *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
312  break;
313  }
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) {
317  goto done;
318  }
319 
320  *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
321  break;
322  }
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) {
326  goto done;
327  }
328 
329  *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
330  break;
331  }
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) {
335  goto done;
336  }
337 
338  *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
339  break;
340  }
341  default:
342  av_log(log_ctx, AV_LOG_ERROR, "Unknown JNI field type\n");
343  ret = AVERROR(EINVAL);
344  goto done;
345  }
346  }
347  }
348 
349 done:
350  if (ret < 0) {
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);
353  }
354 
355  return ret;
356 }
357 
358 int ff_jni_reset_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
359 {
360  int i;
361 
362  for (i = 0; jfields_mapping[i].name; i++) {
363  enum FFJniFieldType type = jfields_mapping[i].type;
364 
365  switch(type) {
366  case FF_JNI_CLASS: {
367  jclass clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset);
368  if (!clazz)
369  continue;
370 
371  if (global) {
372  (*env)->DeleteGlobalRef(env, clazz);
373  } else {
374  (*env)->DeleteLocalRef(env, clazz);
375  }
376 
377  *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
378  break;
379  }
380  case FF_JNI_FIELD: {
381  *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
382  break;
383  }
384  case FF_JNI_STATIC_FIELD: {
385  *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
386  break;
387  }
388  case FF_JNI_METHOD: {
389  *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
390  break;
391  }
392  case FF_JNI_STATIC_METHOD: {
393  *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
394  break;
395  }
396  default:
397  av_log(log_ctx, AV_LOG_ERROR, "Unknown JNI field type\n");
398  }
399  }
400 
401  return 0;
402 }
JNIEnv * ff_jni_attach_env(int *attached, void *log_ctx)
Definition: ffjni.c:37
#define NULL
Definition: coverity.c:32
void av_bprintf(AVBPrint *buf, const char *fmt,...)
Definition: bprint.c:94
int ff_jni_reset_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
Definition: ffjni.c:358
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition: log.h:182
int offset
Definition: ffjni.h:113
enum FFJniFieldType type
Definition: ffjni.h:112
jstring ff_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, void *log_ctx)
Definition: ffjni.c:118
int av_bprint_finalize(AVBPrint *buf, char **ret_str)
Finalize a print buffer.
Definition: bprint.c:235
static const char signature[]
Definition: ipmovie.c:558
HMTX pthread_mutex_t
Definition: os2threads.h:49
uint8_t
static void copy(LZOContext *c, int cnt)
Copies bytes from input to output buffer with checking.
Definition: lzo.c:85
#define av_log(a,...)
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:176
#define AVERROR(e)
Definition: error.h:43
void av_bprint_init(AVBPrint *buf, unsigned size_init, unsigned size_max)
Definition: bprint.c:69
void * av_jni_get_java_vm(void *log_ctx)
Definition: jni.c:75
char * ff_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, void *log_ctx)
Definition: ffjni.c:88
int ff_jni_init_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
Definition: ffjni.c:276
int ff_jni_exception_check(JNIEnv *env, int log, void *log_ctx)
Definition: ffjni.c:243
#define AV_BPRINT_SIZE_AUTOMATIC
static JavaVM * java_vm
Definition: ffjni.c:34
char * av_strdup(const char *s)
Duplicate the string s.
Definition: mem.c:267
GLint GLenum type
Definition: opengl_enc.c:105
int ff_jni_detach_env(void *log_ctx)
Definition: ffjni.c:78
int ff_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, void *log_ctx)
Definition: ffjni.c:132
const char * name
Definition: ffjni.h:109
static pthread_mutex_t lock
Definition: ffjni.c:35
FFJniFieldType
Definition: ffjni.h:93
int mandatory
Definition: ffjni.h:114
#define av_free(p)
static av_always_inline int pthread_mutex_unlock(pthread_mutex_t *mutex)
Definition: os2threads.h:120
static av_always_inline int pthread_mutex_lock(pthread_mutex_t *mutex)
Definition: os2threads.h:113
#define AVERROR_EXTERNAL
Generic error in an external library.
Definition: error.h:57
const char * name
Definition: opengl_enc.c:103