IjkPlayer之JNI基础知识及源码目录

本篇文章是阅读 IjkPlayer 播放器源码的第一篇,记得在之前的工作中也编译过 IjkPlayer,为了后续方便继续阅读其源码,下文中简单汇总下 JNI 开发的一些基础知识,本文主要内容如下:

  1. IjkPlayer编译
  2. IjkPlayer源码目录
  3. NDK介绍
  4. JNI基础知识
  5. 总结

IjkPlayer编译

IjkPlayer 的编译之前单独写过一篇文章,内容还算详细,具体参考如下:

ijkplayer编译过程

IjkPlayer源码目录

IjkPlayer源码目录介绍,具体详见目录后面的说明:

 1├── android                     // android相关目录
 2│   ├── compile-ijk.sh
 3│   ├── contrib                 // ffmpeg编译目录
 4│   │   ├── compile-ffmpeg.sh   // ffmpeg编译脚本
 5│   │   ├── compile-libsoxr.sh  // libsoxr编译脚本
 6│   │   ├── compile-openssl.sh  // openssl编译脚本
 7│   │   ├── ffmpeg-arm64
 8│   │   ├── ffmpeg-armv5
 9│   │   ├── ffmpeg-armv7a
10│   │   ├── ffmpeg-x86
11│   │   ├── ffmpeg-x86_64
12│   ├── ijk-addr2line.sh
13│   ├── ijk-ndk-stack.sh
14│   ├── ijkplayer               // android ijkPlayer源码目录
15│   │   ├── ijkplayer-arm64
16│   │   ├── ijkplayer-armv5
17│   │   ├── ijkplayer-armv7a
18│   │   ├── ijkplayer-example   // ijkPlayer使用案例
19│   │   ├── ijkplayer-exo   
20│   │   ├── ijkplayer-java
21│   │   ├── ijkplayer-x86
22│   │   ├── ijkplayer-x86_64
23├── compile-android-j4a.sh
24├── config                      // ffmpeg编译脚本配置目录
25│   ├── module-default.sh       // ffmpeg默认配置脚本文件
26│   ├── module-lite-hevc.sh     // ffmpeg最小化配置添加hevc功能脚本文件
27│   ├── module-lite.sh          // ffmpeg最小化配置脚本文件
28│   └── module.sh               // ffmpeg当前编译配置脚本文件
29├── doc
30│   └── preflight_checklist.md
31├── extra                       // ijkPlayer使用的开源库的下载目录
32│   ├── ffmpeg                  // ffmpeg
33│   ├── libyuv                  // yuv图像处理库
34│   └── soundtouch              // 音频处理库,主要是变速、变调等
35├── ijkmedia                    // ijkPlayer native层的核心代码
36│   ├── ijkj4a                  // native层和 java层回调的接口层,开源项目jni4android生成
37│   ├── ijkplayer               // ijkPlayer native层代码
38│   ├── ijksdl                  // ijkPlayer音视频渲染SDL库
39│   ├── ijksoundtouch           // ijk封装后的soundtouch库
40│   └── ijkyuv                  // yuv图像处理库
41├── ijkprof                     // ijkplayer的性能调试库
42├── init-android-exo.sh         // 初始化exoPlayer脚本
43├── init-android-j4a.sh         // 初始化j4a脚本
44├── init-android-libsoxr.sh     // 初始化soxr脚本
45├── init-android-libyuv.sh      // 初始化yuv脚本
46├── init-android-openssl.sh     // 初始化openssl脚本
47├── init-android-prof.sh        // 初始化android-ndk-profile脚本
48├── init-android.sh             // 初始化android平台脚本,主要拉取ffmpeg、第三方库等
49├── init-android-soundtouch.sh
50├── init-config.sh              //  ffmpeg脚本文件配置脚本
51├── init-ios-openssl.sh
52├── init-ios.sh
53├── ios                         // IOS相关目录

NDK介绍

对于大部分的应用开发者可能都不会接触到 NDK,但如果涉及到硬件操作的话就不得不使用 NDK 了,使用 NDK 还有一个原因就是 C/C++ 的效率比较高,因此我们可以把一些耗时操作放在 NDK 中进行实现。NDK 是 Native Development Kit 的简称,它是一个工具集,继承了 Android 的交叉编译环境,并提供了一套比较方便的 MakeFile,可以帮助开发者快速开发 C/C++ 的动态库,并自动将 so 和 java 程序打包成 apk 在 Android 中运行。

JNI基础知识

JNI 是 Java Native Interface 的缩写,中文为 Java 本地调用,从 Java 1.1 开始,JNI 标准成为 Java 平台的一部分,它允许 Java 代码和其他语言写的代码进行交互。

JavaVM和JNIEnv

JavaVM 表示 Java 虚拟机,定义在 jni.h 中,每个进程可以有多个 JavaJVM,但 Android 中只允许有一个,这个对应的 javaJVM 对象可以在进程中的各线程间共享,在使用的时候全局保存一个 JavaVM 变量即可共用,其常用的获取方式如下:

  • 第一种:
 1 static JavaVM* g_jvm;
 2 // 第一种
 3 JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
 4 {
 5    JNIEnv* env = NULL;
 6    // 为JavaVM指针赋值
 7    g_jvm = vm;
 8    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
 9        return -1;
10    }
11    // ...
12    return JNI_VERSION_1_4;
13 }
  • 第二种:
1 static JavaVM* g_jvm;
2 JNIEXPORT jint JNICALL Java_manu_com_iptvsamples_ndk_NDKSampleActivity_sum
3  (JNIEnv * env, jobject obj, jint addend1, jint addend2){
4    // 为JavaVM指针赋值
5    env->GetJavaVM(&g_jvm);
6  return addend1 + addend2;
7 }

此外还可以通过 JNI 函数JNI_CreateJavaVM 来创建 JavaVM。JNIEnv 提供了大部分 JNI 函数,Native 函数都接收 JNIEnv 作为第一个参数,JNIEnv 用于线程本地存储,不能在线程之间共享 JNIEnv,如果一段代码没有其他方法获取它的 JNIEnv,可以使用共享 JavaVM 通过 GetEnv 来获取线程的 JNIEnv。

JNI注册方式

注册 JNI 函数主要有两种方式,即静态注册方式和动态注册方式,典型的比如音视频开源项目 ijkPlayer 就是动态注册的方式,后续文中再继续分析。

  • 静态注册

静态注册方式主要就是通过定义的包含 native 方法的 .java 文件,通过 javah 相关命令生成对应的 .h 头文件。在 Activity 中定义本地方法如下:

public native int sum(int addend1, int addend2);

为了方便,将目录切换到项目的 java 目录下,使用如下命令生成供 C/C++ 使用的头文件:

javah -jni com.manu.ndksamples.MainActivity

如果执行报找不到类文件的异常,可以尝试添加 -classpath 参数生成对应头文件,上述本地方法生成的头文件代码如下:

 1/ * DO NOT EDIT THIS FILE - it is machine generated */
 2 #include <jni.h>
 3 /* Header for class manu_com_iptvsamples_ndk_NDKSampleActivity */
 4
 5 #ifndef _Included_com_manu_ndksamples_MainActivity
 6 #define _Included_com_manu_ndksamples_MainActivity
 7 #ifdef __cplusplus
 8 extern "C" {
 9 #endif
10 /*
11 * Class:     com_manu_ndksamples_MainActivity
12 * Method:    sum
13 * Signature: (II)I
14 */
15 JNIEXPORT jint JNICALL Java_com_manu_ndksamples_MainActivity_sum
16  (JNIEnv *, jobject, jint, jint);
17
18 #ifdef __cplusplus
19 }
20 #endif
21 #endif

对应的文件名是包名+类名:com_manu_ndksamples_MainActivity.h,导入头文件,使用 C/C++ 实现 Java 中定义的 native 方法,参考如下:

 1 #include "com_manu_ndksamples_MainActivity.h"
 2
 3 /*
 4 * Class:     com_manu_ndksamples_MainActivity
 5 * Method:    sum
 6 * Signature: (II)I
 7 */
 8 extern "C" JNIEXPORT jint JNICALL Java_com_manu_ndksamples_MainActivity_sum
 9  (JNIEnv * env, jobject obj, jint addend1, jint addend2){
10  return addend1 + addend2;
11 }
  • 动态注册

动态注册方式是通过 JNI 中的 JNINativeMethod 的结构体来保存 native 函数与 JNI 函数之间的一一对应关系,该结构体定义如下:

1 typedef struct {
2    const char* name;
3    const char* signature;
4    void*       fnPtr;
5 } JNINativeMethod;

上面静态注册的 sum 方法也可以用动态注册的方式,如下:

 1 #include <jni.h>
 2 #include <cassert>
 3 #include <iostream>
 4
 5 using namespace std;
 6
 7 #define JNI_CLASS "com/manu/ndksamples/MainActivity"
 8
 9 static JavaVM *g_jvm;
10
11 static jint sample_sum(JNIEnv *env, jobject thiz, jint add1, jint add2) {
12    cout << "sample_sum" << endl;
13    return add1 + add2;
14}
15
16 static JNINativeMethod g_methods[] = {
17        {"sum", "(II)I", (void *) sample_sum}
18 };
19
20 JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
21    JNIEnv *env = nullptr;
22    g_jvm = vm;
23    if ((*vm).GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
24        return -1;
25    }
26    assert(env != nullptr);
27    jclass clazz = (*env).FindClass(JNI_CLASS);
28    // 注册函数对应关系
29    (*env).RegisterNatives(clazz, g_methods, sizeof(g_methods) / sizeof((g_methods)[0]));
30    return JNI_VERSION_1_4;
31 }
32
33 JNIEXPORT void JNI_OnUnload(JavaVM *jvm, void *reserved) {
34    // JNI_OnUnload
35 }

上述代码中函数 sample_sum 与 Java 中的 native 方法 sum 相对应,这种对应关系是通过 RegisterNatives 函数来进行注册的,其基本流程是当调用 System.loadLibrary 加载库的时候会去查找 JNI_OnLoad 这个函数,然后再该函数回调中进行注册,同样的在JNI_OnUnload中进行销毁操作。

总结

本文主要介绍了 IjkPlayer 的源码目录、IjkPlayer 的编译,以及一些必备的 JNI 相关的基础知识,下篇将正式开始 IjkPlayer 源码的阅读。

作者: jzman,个人微信公众号:躬行之 ,可以关注一起交流学习.

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论