You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

486 lines
14KB

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