]> git.sesse.net Git - ffmpeg/blob - libavcodec/ffjni.c
Merge commit '74beead9bd596180bcac6108548fc0a86d8eb4ae'
[ffmpeg] / libavcodec / ffjni.c
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
44     pthread_mutex_lock(&lock);
45     if (java_vm == NULL) {
46         java_vm = av_jni_get_java_vm(log_ctx);
47     }
48     pthread_mutex_unlock(&lock);
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
149     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
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 }