lavc: add JNI support
[ffmpeg.git] / 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 <dlfcn.h>
24 #include <jni.h>
25 #include <pthread.h>
26 #include <stdlib.h>
27
28 #include "libavutil/bprint.h"
29 #include "libavutil/log.h"
30
31 #include "config.h"
32 #include "jni.h"
33 #include "ffjni.h"
34
35 static JavaVM *java_vm = NULL;
36 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
37
38 /**
39  * Check if JniInvocation has been initialized. Only available on
40  * Android >= 4.4.
41  *
42  * @param log_ctx context used for logging, can be NULL
43  * @return 0 on success, < 0 otherwise
44  */
45 static int check_jni_invocation(void *log_ctx)
46 {
47     int ret = AVERROR_EXTERNAL;
48     void *handle = NULL;
49     void **jni_invocation = NULL;
50
51     handle = dlopen(NULL, RTLD_LOCAL);
52     if (!handle) {
53         goto done;
54     }
55
56     jni_invocation = (void **)dlsym(handle, "_ZN13JniInvocation15jni_invocation_E");
57     if (!jni_invocation) {
58         av_log(log_ctx, AV_LOG_ERROR, "Could not find JniInvocation::jni_invocation_ symbol\n");
59         goto done;
60     }
61
62     ret = !(jni_invocation != NULL && *jni_invocation != NULL);
63
64 done:
65     if (handle) {
66         dlclose(handle);
67     }
68
69     return ret;
70 }
71
72 /**
73  * Return created Java virtual machine using private JNI_GetCreatedJavaVMs
74  * function from the specified library name.
75  *
76  * @param name library name used for symbol lookups, can be NULL
77  * @param log_ctx context used for logging, can be NULL
78  * @return the current Java virtual machine in use
79  */
80 static JavaVM *get_java_vm(const char *name, void *log_ctx)
81 {
82     JavaVM *vm = NULL;
83     jsize nb_vm = 0;
84
85     void *handle = NULL;
86     jint (*get_created_java_vms) (JavaVM ** vmBuf, jsize bufLen, jsize *nVMs) = NULL;
87
88     handle = dlopen(name, RTLD_LOCAL);
89     if (!handle) {
90         return NULL;
91     }
92
93     get_created_java_vms = (jint (*)(JavaVM **, jsize, jsize *)) dlsym(handle, "JNI_GetCreatedJavaVMs");
94     if (!get_created_java_vms) {
95         av_log(log_ctx, AV_LOG_ERROR, "Could not find JNI_GetCreatedJavaVMs symbol in library '%s'\n", name);
96         goto done;
97     }
98
99     if (get_created_java_vms(&vm, 1, &nb_vm) != JNI_OK) {
100         av_log(log_ctx, AV_LOG_ERROR, "Could not get created Java virtual machines\n");
101         goto done;
102     }
103
104 done:
105     if (handle) {
106         dlclose(handle);
107     }
108
109     return vm;
110 }
111
112 JNIEnv *ff_jni_attach_env(int *attached, void *log_ctx)
113 {
114     int ret = 0;
115     JNIEnv *env = NULL;
116
117     *attached = 0;
118
119     pthread_mutex_lock(&lock);
120     if (java_vm == NULL && (java_vm = av_jni_get_java_vm(log_ctx)) == NULL) {
121
122         av_log(log_ctx, AV_LOG_INFO, "Retrieving current Java virtual machine using Android JniInvocation wrapper\n");
123         if (check_jni_invocation(log_ctx) == 0) {
124             if ((java_vm = get_java_vm(NULL, log_ctx)) != NULL ||
125                 (java_vm = get_java_vm("libdvm.so", log_ctx)) != NULL ||
126                 (java_vm = get_java_vm("libart.so", log_ctx)) != NULL) {
127                 av_log(log_ctx, AV_LOG_INFO, "Found Java virtual machine using Android JniInvocation wrapper\n");
128             }
129         }
130     }
131     pthread_mutex_unlock(&lock);
132
133     if (!java_vm) {
134         av_log(log_ctx, AV_LOG_ERROR, "Could not retrieve a Java virtual machine\n");
135         return NULL;
136     }
137
138     ret = (*java_vm)->GetEnv(java_vm, (void **)&env, JNI_VERSION_1_6);
139     switch(ret) {
140     case JNI_EDETACHED:
141         if ((*java_vm)->AttachCurrentThread(java_vm, &env, NULL) != 0) {
142             av_log(log_ctx, AV_LOG_ERROR, "Failed to attach the JNI environment to the current thread\n");
143             env = NULL;
144         } else {
145             *attached = 1;
146         }
147         break;
148     case JNI_OK:
149         break;
150     case JNI_EVERSION:
151         av_log(log_ctx, AV_LOG_ERROR, "The specified JNI version is not supported\n");
152         break;
153     default:
154         av_log(log_ctx, AV_LOG_ERROR, "Failed to get the JNI environment attached to this thread");
155         break;
156     }
157
158     return env;
159 }
160
161 int ff_jni_detach_env(void *log_ctx)
162 {
163     if (java_vm == NULL) {
164         av_log(log_ctx, AV_LOG_ERROR, "No Java virtual machine has been registered\n");
165         return AVERROR(EINVAL);
166     }
167
168     return (*java_vm)->DetachCurrentThread(java_vm);
169 }
170
171 char *ff_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, void *log_ctx)
172 {
173     char *ret = NULL;
174     const char *utf_chars = NULL;
175
176     jboolean copy = 0;
177
178     if (!string) {
179         return NULL;
180     }
181
182     utf_chars = (*env)->GetStringUTFChars(env, string, &copy);
183     if ((*env)->ExceptionCheck(env)) {
184         (*env)->ExceptionClear(env);
185         av_log(log_ctx, AV_LOG_ERROR, "String.getStringUTFChars() threw an exception\n");
186         return NULL;
187     }
188
189     ret = av_strdup(utf_chars);
190
191     (*env)->ReleaseStringUTFChars(env, string, utf_chars);
192     if ((*env)->ExceptionCheck(env)) {
193         (*env)->ExceptionClear(env);
194         av_log(log_ctx, AV_LOG_ERROR, "String.releaseStringUTFChars() threw an exception\n");
195         return NULL;;
196     }
197
198     return ret;
199 }
200
201 jstring ff_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, void *log_ctx)
202 {
203     jstring ret;
204
205     ret = (*env)->NewStringUTF(env, utf_chars);
206     if ((*env)->ExceptionCheck(env)) {
207         (*env)->ExceptionClear(env);
208         av_log(log_ctx, AV_LOG_ERROR, "NewStringUTF() threw an exception\n");
209         return NULL;
210     }
211
212     return ret;
213 }
214
215 int ff_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, void *log_ctx)
216 {
217     int ret = 0;
218
219     AVBPrint bp;
220
221     char *name = NULL;
222     char *message = NULL;
223
224     jclass class_class = NULL;
225     jmethodID get_name_id = NULL;
226
227     jclass exception_class = NULL;
228     jmethodID get_message_id = NULL;
229
230     jstring string;
231
232     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
233
234     exception_class = (*env)->GetObjectClass(env, exception);
235     if ((*env)->ExceptionCheck(env)) {
236         (*env)->ExceptionClear(env);
237         av_log(log_ctx, AV_LOG_ERROR, "Could not find Throwable class\n");
238         ret = AVERROR_EXTERNAL;
239         goto done;
240     }
241
242     class_class = (*env)->GetObjectClass(env, exception_class);
243     if ((*env)->ExceptionCheck(env)) {
244         (*env)->ExceptionClear(env);
245         av_log(log_ctx, AV_LOG_ERROR, "Could not find Throwable class's class\n");
246         ret = AVERROR_EXTERNAL;
247         goto done;
248     }
249
250     get_name_id = (*env)->GetMethodID(env, class_class, "getName", "()Ljava/lang/String;");
251     if ((*env)->ExceptionCheck(env)) {
252         (*env)->ExceptionClear(env);
253         av_log(log_ctx, AV_LOG_ERROR, "Could not find method Class.getName()\n");
254         ret = AVERROR_EXTERNAL;
255         goto done;
256     }
257
258     string = (*env)->CallObjectMethod(env, exception_class, get_name_id);
259     if ((*env)->ExceptionCheck(env)) {
260         (*env)->ExceptionClear(env);
261         av_log(log_ctx, AV_LOG_ERROR, "Class.getName() threw an exception\n");
262         ret = AVERROR_EXTERNAL;
263         goto done;
264     }
265
266     if (string) {
267         name = ff_jni_jstring_to_utf_chars(env, string, log_ctx);
268         (*env)->DeleteLocalRef(env, string);
269         string = NULL;
270     }
271
272     get_message_id = (*env)->GetMethodID(env, exception_class, "getMessage", "()Ljava/lang/String;");
273     if ((*env)->ExceptionCheck(env)) {
274         (*env)->ExceptionClear(env);
275         av_log(log_ctx, AV_LOG_ERROR, "Could not find method java/lang/Throwable.getMessage()\n");
276         ret = AVERROR_EXTERNAL;
277         goto done;
278     }
279
280     string = (*env)->CallObjectMethod(env, exception, get_message_id);
281     if ((*env)->ExceptionCheck(env)) {
282         (*env)->ExceptionClear(env);
283         av_log(log_ctx, AV_LOG_ERROR, "Throwable.getMessage() threw an exception\n");
284         ret = AVERROR_EXTERNAL;
285         goto done;
286     }
287
288     if (string) {
289         message = ff_jni_jstring_to_utf_chars(env, string, log_ctx);
290         (*env)->DeleteLocalRef(env, string);
291         string = NULL;
292     }
293
294     if (name && message) {
295         av_bprintf(&bp, "%s: %s", name, message);
296     } else if (name && !message) {
297         av_bprintf(&bp, "%s occured", name);
298     } else if (!name && message) {
299         av_bprintf(&bp, "Exception: %s", message);
300     } else {
301         av_log(log_ctx, AV_LOG_WARNING, "Could not retreive exception name and message\n");
302         av_bprintf(&bp, "Exception occured");
303     }
304
305     ret = av_bprint_finalize(&bp, error);
306 done:
307
308     av_free(name);
309     av_free(message);
310
311     if (class_class) {
312         (*env)->DeleteLocalRef(env, class_class);
313     }
314
315     if (exception_class) {
316         (*env)->DeleteLocalRef(env, exception_class);
317     }
318
319     if (string) {
320         (*env)->DeleteLocalRef(env, string);
321     }
322
323     return ret;
324 }
325
326 int ff_jni_exception_check(JNIEnv *env, int log, void *log_ctx)
327 {
328     int ret;
329
330     jthrowable exception;
331
332     char *message = NULL;
333
334     if (!(*(env))->ExceptionCheck((env))) {
335         return 0;
336     }
337
338     if (!log) {
339         (*(env))->ExceptionClear((env));
340         return -1;
341     }
342
343     exception = (*env)->ExceptionOccurred(env);
344     (*(env))->ExceptionClear((env));
345
346     if ((ret = ff_jni_exception_get_summary(env, exception, &message, log_ctx)) < 0) {
347         (*env)->DeleteLocalRef(env, exception);
348         return ret;
349     }
350
351     (*env)->DeleteLocalRef(env, exception);
352
353     av_log(log_ctx, AV_LOG_ERROR, "%s\n", message);
354     av_free(message);
355
356     return -1;
357 }
358
359 int ff_jni_init_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
360 {
361     int i, ret = 0;
362     jclass last_clazz = NULL;
363
364     for (i = 0; jfields_mapping[i].name; i++) {
365         int mandatory = jfields_mapping[i].mandatory;
366         enum FFJniFieldType type = jfields_mapping[i].type;
367
368         if (type == FF_JNI_CLASS) {
369             jclass clazz;
370
371             last_clazz = NULL;
372
373             clazz = (*env)->FindClass(env, jfields_mapping[i].name);
374             if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
375                 goto done;
376             }
377
378             last_clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) =
379                     global ? (*env)->NewGlobalRef(env, clazz) : clazz;
380         } else {
381
382             if (!last_clazz) {
383                 ret = AVERROR_EXTERNAL;
384                 break;
385             }
386
387             switch(type) {
388             case FF_JNI_FIELD: {
389                 jfieldID field_id = (*env)->GetFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
390                 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
391                     goto done;
392                 }
393
394                 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
395                 break;
396             }
397             case FF_JNI_STATIC_FIELD: {
398                 jfieldID field_id = (*env)->GetStaticFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
399                 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
400                     goto done;
401                 }
402
403                 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
404                 break;
405             }
406             case FF_JNI_METHOD: {
407                 jmethodID method_id = (*env)->GetMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
408                 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
409                     goto done;
410                 }
411
412                 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
413                 break;
414             }
415             case FF_JNI_STATIC_METHOD: {
416                 jmethodID method_id = (*env)->GetStaticMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
417                 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
418                     goto done;
419                 }
420
421                 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
422                 break;
423             }
424             default:
425                 av_log(log_ctx, AV_LOG_ERROR, "Unknown JNI field type\n");
426                 ret = AVERROR(EINVAL);
427                 goto done;
428             }
429         }
430     }
431
432 done:
433     if (ret < 0) {
434         /* reset jfields in case of failure so it does not leak references */
435         ff_jni_reset_jfields(env, jfields, jfields_mapping, global, log_ctx);
436     }
437
438     return ret;
439 }
440
441 int ff_jni_reset_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
442 {
443     int i;
444
445     for (i = 0; jfields_mapping[i].name; i++) {
446         enum FFJniFieldType type = jfields_mapping[i].type;
447
448         switch(type) {
449         case FF_JNI_CLASS: {
450             jclass clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset);
451             if (!clazz)
452                 continue;
453
454             if (global) {
455                 (*env)->DeleteGlobalRef(env, clazz);
456             } else {
457                 (*env)->DeleteLocalRef(env, clazz);
458             }
459
460             *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
461             break;
462         }
463         case FF_JNI_FIELD: {
464             *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
465             break;
466         }
467         case FF_JNI_STATIC_FIELD: {
468             *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
469             break;
470         }
471         case FF_JNI_METHOD: {
472             *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
473             break;
474         }
475         case FF_JNI_STATIC_METHOD: {
476             *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
477             break;
478         }
479         default:
480             av_log(log_ctx, AV_LOG_ERROR, "Unknown JNI field type\n");
481         }
482     }
483
484     return 0;
485 }