]> git.sesse.net Git - ffmpeg/blob - libavcodec/ffjni.c
avformat/avio: Add Metacube support
[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;
35 static pthread_key_t current_env;
36 static pthread_once_t once = PTHREAD_ONCE_INIT;
37 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
38
39 static void jni_detach_env(void *data)
40 {
41     if (java_vm) {
42         (*java_vm)->DetachCurrentThread(java_vm);
43     }
44 }
45
46 static void jni_create_pthread_key(void)
47 {
48     pthread_key_create(&current_env, jni_detach_env);
49 }
50
51 JNIEnv *ff_jni_get_env(void *log_ctx)
52 {
53     int ret = 0;
54     JNIEnv *env = NULL;
55
56     pthread_mutex_lock(&lock);
57     if (java_vm == NULL) {
58         java_vm = av_jni_get_java_vm(log_ctx);
59     }
60
61     if (!java_vm) {
62         av_log(log_ctx, AV_LOG_ERROR, "No Java virtual machine has been registered\n");
63         goto done;
64     }
65
66     pthread_once(&once, jni_create_pthread_key);
67
68     if ((env = pthread_getspecific(current_env)) != NULL) {
69         goto done;
70     }
71
72     ret = (*java_vm)->GetEnv(java_vm, (void **)&env, JNI_VERSION_1_6);
73     switch(ret) {
74     case JNI_EDETACHED:
75         if ((*java_vm)->AttachCurrentThread(java_vm, &env, NULL) != 0) {
76             av_log(log_ctx, AV_LOG_ERROR, "Failed to attach the JNI environment to the current thread\n");
77             env = NULL;
78         } else {
79             pthread_setspecific(current_env, env);
80         }
81         break;
82     case JNI_OK:
83         break;
84     case JNI_EVERSION:
85         av_log(log_ctx, AV_LOG_ERROR, "The specified JNI version is not supported\n");
86         break;
87     default:
88         av_log(log_ctx, AV_LOG_ERROR, "Failed to get the JNI environment attached to this thread\n");
89         break;
90     }
91
92 done:
93     pthread_mutex_unlock(&lock);
94     return env;
95 }
96
97 char *ff_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, void *log_ctx)
98 {
99     char *ret = NULL;
100     const char *utf_chars = NULL;
101
102     jboolean copy = 0;
103
104     if (!string) {
105         return NULL;
106     }
107
108     utf_chars = (*env)->GetStringUTFChars(env, string, &copy);
109     if ((*env)->ExceptionCheck(env)) {
110         (*env)->ExceptionClear(env);
111         av_log(log_ctx, AV_LOG_ERROR, "String.getStringUTFChars() threw an exception\n");
112         return NULL;
113     }
114
115     ret = av_strdup(utf_chars);
116
117     (*env)->ReleaseStringUTFChars(env, string, utf_chars);
118     if ((*env)->ExceptionCheck(env)) {
119         (*env)->ExceptionClear(env);
120         av_log(log_ctx, AV_LOG_ERROR, "String.releaseStringUTFChars() threw an exception\n");
121         return NULL;
122     }
123
124     return ret;
125 }
126
127 jstring ff_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, void *log_ctx)
128 {
129     jstring ret;
130
131     ret = (*env)->NewStringUTF(env, utf_chars);
132     if ((*env)->ExceptionCheck(env)) {
133         (*env)->ExceptionClear(env);
134         av_log(log_ctx, AV_LOG_ERROR, "NewStringUTF() threw an exception\n");
135         return NULL;
136     }
137
138     return ret;
139 }
140
141 int ff_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, void *log_ctx)
142 {
143     int ret = 0;
144
145     AVBPrint bp;
146
147     char *name = NULL;
148     char *message = NULL;
149
150     jclass class_class = NULL;
151     jmethodID get_name_id = NULL;
152
153     jclass exception_class = NULL;
154     jmethodID get_message_id = NULL;
155
156     jstring string = NULL;
157
158     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
159
160     exception_class = (*env)->GetObjectClass(env, exception);
161     if ((*env)->ExceptionCheck(env)) {
162         (*env)->ExceptionClear(env);
163         av_log(log_ctx, AV_LOG_ERROR, "Could not find Throwable class\n");
164         ret = AVERROR_EXTERNAL;
165         goto done;
166     }
167
168     class_class = (*env)->GetObjectClass(env, exception_class);
169     if ((*env)->ExceptionCheck(env)) {
170         (*env)->ExceptionClear(env);
171         av_log(log_ctx, AV_LOG_ERROR, "Could not find Throwable class's class\n");
172         ret = AVERROR_EXTERNAL;
173         goto done;
174     }
175
176     get_name_id = (*env)->GetMethodID(env, class_class, "getName", "()Ljava/lang/String;");
177     if ((*env)->ExceptionCheck(env)) {
178         (*env)->ExceptionClear(env);
179         av_log(log_ctx, AV_LOG_ERROR, "Could not find method Class.getName()\n");
180         ret = AVERROR_EXTERNAL;
181         goto done;
182     }
183
184     string = (*env)->CallObjectMethod(env, exception_class, get_name_id);
185     if ((*env)->ExceptionCheck(env)) {
186         (*env)->ExceptionClear(env);
187         av_log(log_ctx, AV_LOG_ERROR, "Class.getName() threw an exception\n");
188         ret = AVERROR_EXTERNAL;
189         goto done;
190     }
191
192     if (string) {
193         name = ff_jni_jstring_to_utf_chars(env, string, log_ctx);
194         (*env)->DeleteLocalRef(env, string);
195         string = NULL;
196     }
197
198     get_message_id = (*env)->GetMethodID(env, exception_class, "getMessage", "()Ljava/lang/String;");
199     if ((*env)->ExceptionCheck(env)) {
200         (*env)->ExceptionClear(env);
201         av_log(log_ctx, AV_LOG_ERROR, "Could not find method java/lang/Throwable.getMessage()\n");
202         ret = AVERROR_EXTERNAL;
203         goto done;
204     }
205
206     string = (*env)->CallObjectMethod(env, exception, get_message_id);
207     if ((*env)->ExceptionCheck(env)) {
208         (*env)->ExceptionClear(env);
209         av_log(log_ctx, AV_LOG_ERROR, "Throwable.getMessage() threw an exception\n");
210         ret = AVERROR_EXTERNAL;
211         goto done;
212     }
213
214     if (string) {
215         message = ff_jni_jstring_to_utf_chars(env, string, log_ctx);
216         (*env)->DeleteLocalRef(env, string);
217         string = NULL;
218     }
219
220     if (name && message) {
221         av_bprintf(&bp, "%s: %s", name, message);
222     } else if (name && !message) {
223         av_bprintf(&bp, "%s occurred", name);
224     } else if (!name && message) {
225         av_bprintf(&bp, "Exception: %s", message);
226     } else {
227         av_log(log_ctx, AV_LOG_WARNING, "Could not retrieve exception name and message\n");
228         av_bprintf(&bp, "Exception occurred");
229     }
230
231     ret = av_bprint_finalize(&bp, error);
232 done:
233
234     av_free(name);
235     av_free(message);
236
237     if (class_class) {
238         (*env)->DeleteLocalRef(env, class_class);
239     }
240
241     if (exception_class) {
242         (*env)->DeleteLocalRef(env, exception_class);
243     }
244
245     if (string) {
246         (*env)->DeleteLocalRef(env, string);
247     }
248
249     return ret;
250 }
251
252 int ff_jni_exception_check(JNIEnv *env, int log, void *log_ctx)
253 {
254     int ret;
255
256     jthrowable exception;
257
258     char *message = NULL;
259
260     if (!(*(env))->ExceptionCheck((env))) {
261         return 0;
262     }
263
264     if (!log) {
265         (*(env))->ExceptionClear((env));
266         return -1;
267     }
268
269     exception = (*env)->ExceptionOccurred(env);
270     (*(env))->ExceptionClear((env));
271
272     if ((ret = ff_jni_exception_get_summary(env, exception, &message, log_ctx)) < 0) {
273         (*env)->DeleteLocalRef(env, exception);
274         return ret;
275     }
276
277     (*env)->DeleteLocalRef(env, exception);
278
279     av_log(log_ctx, AV_LOG_ERROR, "%s\n", message);
280     av_free(message);
281
282     return -1;
283 }
284
285 int ff_jni_init_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
286 {
287     int i, ret = 0;
288     jclass last_clazz = NULL;
289
290     for (i = 0; jfields_mapping[i].name; i++) {
291         int mandatory = jfields_mapping[i].mandatory;
292         enum FFJniFieldType type = jfields_mapping[i].type;
293
294         if (type == FF_JNI_CLASS) {
295             jclass clazz;
296
297             last_clazz = NULL;
298
299             clazz = (*env)->FindClass(env, jfields_mapping[i].name);
300             if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
301                 goto done;
302             }
303
304             last_clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) =
305                     global ? (*env)->NewGlobalRef(env, clazz) : clazz;
306
307             if (global) {
308                 (*env)->DeleteLocalRef(env, clazz);
309             }
310
311         } else {
312
313             if (!last_clazz) {
314                 ret = AVERROR_EXTERNAL;
315                 break;
316             }
317
318             switch(type) {
319             case FF_JNI_FIELD: {
320                 jfieldID field_id = (*env)->GetFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
321                 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
322                     goto done;
323                 }
324
325                 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
326                 break;
327             }
328             case FF_JNI_STATIC_FIELD: {
329                 jfieldID field_id = (*env)->GetStaticFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
330                 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
331                     goto done;
332                 }
333
334                 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
335                 break;
336             }
337             case FF_JNI_METHOD: {
338                 jmethodID method_id = (*env)->GetMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
339                 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
340                     goto done;
341                 }
342
343                 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
344                 break;
345             }
346             case FF_JNI_STATIC_METHOD: {
347                 jmethodID method_id = (*env)->GetStaticMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
348                 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
349                     goto done;
350                 }
351
352                 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
353                 break;
354             }
355             default:
356                 av_log(log_ctx, AV_LOG_ERROR, "Unknown JNI field type\n");
357                 ret = AVERROR(EINVAL);
358                 goto done;
359             }
360
361             ret = 0;
362         }
363     }
364
365 done:
366     if (ret < 0) {
367         /* reset jfields in case of failure so it does not leak references */
368         ff_jni_reset_jfields(env, jfields, jfields_mapping, global, log_ctx);
369     }
370
371     return ret;
372 }
373
374 int ff_jni_reset_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
375 {
376     int i;
377
378     for (i = 0; jfields_mapping[i].name; i++) {
379         enum FFJniFieldType type = jfields_mapping[i].type;
380
381         switch(type) {
382         case FF_JNI_CLASS: {
383             jclass clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset);
384             if (!clazz)
385                 continue;
386
387             if (global) {
388                 (*env)->DeleteGlobalRef(env, clazz);
389             } else {
390                 (*env)->DeleteLocalRef(env, clazz);
391             }
392
393             *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
394             break;
395         }
396         case FF_JNI_FIELD: {
397             *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
398             break;
399         }
400         case FF_JNI_STATIC_FIELD: {
401             *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
402             break;
403         }
404         case FF_JNI_METHOD: {
405             *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
406             break;
407         }
408         case FF_JNI_STATIC_METHOD: {
409             *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
410             break;
411         }
412         default:
413             av_log(log_ctx, AV_LOG_ERROR, "Unknown JNI field type\n");
414         }
415     }
416
417     return 0;
418 }