「音视频直播技术」看ijkplayer如何使用JNI
cac55 2024-10-09 08:18 34 浏览 0 评论
前言
ijklayer可以说是目前最火的一款移动端播放器了。它同时支持Android和iOS,它之所以如此流行,主要是代码写的太美了,我认为把它当作艺术品也不过分。
ijkplayer为了提高性能做了大量的优化,其中一个关键点是使用了JNI。播边器里最关键的部分全部由C来实现。
今天我们就来看看 jikplayer 是如何使用JNI的。
导入动态库
ijkplayer 创建 IJKMediaPlayer 对象时,在其构造函数里会调到 loadLibrariesOnce 方法。代码如下:
public static void loadLibrariesOnce(IjkLibLoader libLoader) { ...... libLoader.loadLibrary("ijkffmpeg"); //加载 ffmpeg 用于解码 libLoader.loadLibrary("ijksdl"); // 加载 sdk 用于渲染 libLoader.loadLibrary("ijkplayer"); // 加载 ijkplayer 核心播放器 ...... }
其中,loadLibrariesOnce方法中的libLoader是ijkplayer定义的IjkLibLoader类对象。该对象的 loadLibrary 方法最终会调用 System.loadLibrary 函数完成共享库的加载。
经过上面操作后 ijkffmpeg、ijksdl及ijkplayer就被加载到JavaVM里了。
在Android系统下,每一个进程只能有一个JavaVM。
我们第一步看下在ijkplayer里,如何通过 Java代码调到 C/C++接口。
在Java层定义本地方法
想通过 Java 调用 C/C++ 接口,首先需要让 Java 程序知道都有哪些 C/C++ 接口可用。这有点像C/C++中常说的符号表(名子与地址的对应关系表)。如何能做到这点呢?方法很简单,就是在 Java 类方法的前边加上 "native" 关键字。我们看一下 IJKPlayer 都提供了哪些本地方法吧:
...... private native void _setDataSource(IMediaDataSource mediaDataSource); public native void _prepareAsync() throws IllegalStateException; private native void _start() throws IllegalStateException; private native void _stop() throws IllegalStateException; private native void _pause() throws IllegalStateException; public native void seekTo(long msec) throws IllegalStateException; public native long getDuration(); private native void _release(); public native void setVolume(float leftVolume, float rightVolume); private static native void native_init(); ......
这一步是不是非常简单?
当然,只做到这一步还无法调用 C/C++接口,因为你还没告诉JavaVM你的C/C++接口在哪儿呢。
注册C/C++方法
仅在Java层定义本地方法只完成了工作的一半。当Java代码真正调用 “native” 方法时,JavaVM虚拟机会在符号表中查找有没有 Java 程序想调用的函数。如果此时没有的话,JavaVM 就会报错。所以现在我们要将 C/C++ 提供的接口注册到 JavaVM中。
首先,建好函数对应表。此表中的每一项都包括三个元素,分别是 外部调用的接口名、signature、内部真正的实现函数。
signature 后面有专门的讲解。
代码如下:
static JNINativeMethod g_methods[] = { ...... { "_setDataSource", "(Ltv/danmaku/ijk/media/player/misc/IMediaDataSource;)V", (void *)IjkMediaPlayer_setDataSourceCallback}, { "_prepareAsync", "()V", (void *) IjkMediaPlayer_prepareAsync }, { "_start", "()V", (void *) IjkMediaPlayer_start }, { "_stop", "()V", (void *) IjkMediaPlayer_stop }, { "seekTo", "(J)V", (void *) IjkMediaPlayer_seekTo }, { "_pause", "()V", (void *) IjkMediaPlayer_pause },{ "getDuration", "()J", (void *) IjkMediaPlayer_getDuration }, { "_release", "()V", (void *) IjkMediaPlayer_release }, { "setVolume", "(FF)V", (void *) IjkMediaPlayer_setVolume }, { "native_init", "()V", (void *) IjkMediaPlayer_native_init }, ...... };
看看这里的外部调用函数名是不是与上面在 Java 层定义的方法名是一样的呢?只有一样它们之前才能建立起对应关系来。
然后,将上面表中的方法注册到JavaVM中。代码如下:
...... //注册native方法,并与 IjkMediaPlayer 关联起来。 //g_clazz.clazz 存放的是 IjkMediaPlayer 类的 jclass 对象 //g_methods 就是上面定义的函数表//NELEM(g_methods) 计算函数表中一共有几项 (*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) ); ......
ijkplayer通过FindClass找到IjkMediaPlayer类的 jclass 对象,再通过RegisterNatives函数将C/C++接口注册到JavaVM中,并与IjkMediaPlayer类绑定在一起。
在哪儿注册最好
上面我们知道了如何注册C/C++方法,那么在什么地方注册好呢?答案是在 JNI_OnLoad 函数中。因为在加载动态链接库时,JavaVM会主动调用JNI_OnLoad(JavaVM * jvm, void * reserved)。看一下ijkplayer的实现:
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv* env = NULL; g_jvm = vm; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; } ...... // FindClass returns GlobleReference IJK_FIND_JAVA_CLASS(env, g_clazz.clazz, JNI_CLASS_IJKPLAYER); (*env)->RegisterNatives(env, g_clazz.clazz, g_methods, NELEM(g_methods) ); ...... return JNI_VERSION_1_4; } JNIEXPORT void JNI_OnUnload(JavaVM *jvm, void *reserved) { ...... }
在 JNI_OnLoad 函数中首先获取 JNIEnv,之后找到 IjkMediaPlayer 类,最后注册C/C++方法,并将注册的方法与IjkMediaPlayer类关联起来。
当然,有了 JNI_OnLoad 还要有 JNI_OnUnload 函数。它在共享库被卸载时调用,可以在这里释放一些资源。
通过上面的操作我们就可以从 Java 调用 C++的代码了。有没有赶快去试试的冲动?先别急,现在只介绍了如何从 Java 调用 C/C++的方法。那么反回来如何从 C/C++ 调 Java 代码呢?
C/C++调用Java方法
ijkplayer 会使用C调用android下的 MediaCodec 类中的方法。首先,通过 FindClass 获取MediaCodec的 jclass 对象。然后设置该对象为全局引用,并将它的本地引用删除。
这些方法的调用都要做异常判断,如果出现异常所有的结果都是无效的值。
我们就以这个为例子,看一下它是如何从C调用的java方法吧。
...... //对异常的处理 bool J4A_ExceptionCheck__catchAll(JNIEnv *env) { if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionDescribe(env); (*env)->ExceptionClear(env); return true; } return false; } //通过 FindClass 得到jclass对象 jclass J4A_FindClass__catchAll(JNIEnv *env, const char *class_sign) { jclass clazz = (*env)->FindClass(env, class_sign); ...... } //拿到 jclass 对象,并设置其为全局引用 jclass J4A_FindClass__asGlobalRef__catchAll(JNIEnv *env, const char *class_sign) { jclass clazz_global = NULL; jclass clazz = J4A_FindClass__catchAll(env, class_sign); ...... //设置为全局引用 clazz_global = J4A_NewGlobalRef__catchAll(env, clazz); ...... fail: J4A_DeleteLocalRef__p(env, &clazz); return clazz_global; } ...... //设置要获取的类名 sign = "android/media/MediaCodec"; class_J4AC_android_media_MediaCodec.id = J4A_FindClass__asGlobalRef__catchAll(env, sign); ......
获得了 jclass 后,就可以通过 Get<Type>MethodID 获取类方法的jmethodID对象。
...... jmethodID J4A_GetStaticMethodID__catchAll(JNIEnv *env, jclass clazz, const char *method_name, const char *method_sign) { jmethodID method_id = (*env)->GetStaticMethodID(env, clazz, method_name, method_sign); ...... fail: return method_id; } ...... class_id = class_J4AC_android_media_MediaCodec.id; //jclass name = "createByCodecName"; //方法名 sign = "(Ljava/lang/String;)Landroid/media/MediaCodec;"; //signature class_J4AC_android_media_MediaCodec.method_createByCodecName = J4A_GetStaticMethodID__catchAll(env, class_id, name, sign); ......
最后,通过 Call<Type>Method 调用 java 方法。
...... jobject J4AC_android_media_MediaCodec__createByCodecName(JNIEnv *env, jstring name) { return (*env)->CallStaticObjectMethod(env, class_J4AC_android_media_MediaCodec.id, class_J4AC_android_media_MediaCodec.method_createByCodecName, name); } ......
现在 C/C++ 也可以调用 Java 方法了。
最后,我们再来看一下C/C++如何访问 java 的字段吧,这个就更简单了。
C/C++访问Java字段
有了 C/C++访问Java的基础,再看访问Java字段就容易多了。它也是先获取 jclass, 之后通过 jclass 得到 jfieldID,最终 Get/Set java 字段。jclass的获取我们就不讲了,重点说说获取 jfieldID 和 Get/Set。
...... jfieldID J4A_GetFieldID__catchAll(JNIEnv *env, jclass clazz, const char *field_name, const char *field_sign) { //获得 jfieldID jfieldID field_id = (*env)->GetFieldID(env, clazz, field_name, field_sign); //异常判断 if (J4A_ExceptionCheck__catchAll(env) || !field_id) { ...... } fail: return field_id; } ...... class_id = class_J4AC_android_media_MediaCodec__BufferInfo.id; //jclass name = "flags"; // java 字段名 sign = "I"; // signature class_J4AC_android_media_MediaCodec__BufferInfo.field_flags = J4A_GetFieldID__catchAll(env, class_id, name, sign); ......
上面的代码通过GetFieldID方法就得到了我们想要的 jfieldID。下一步看看如何进行 Get/Set。
...... jint J4AC_android_media_MediaCodec__BufferInfo__flags__get(JNIEnv *env, jobject thiz) { return (*env)->GetIntField(env, thiz, class_J4AC_android_media_MediaCodec__BufferInfo.field_flags); } ...... void J4AC_android_media_MediaCodec__BufferInfo__flags__set(JNIEnv *env, jobject thiz, jint value) { (*env)->SetIntField(env, thiz, class_J4AC_android_media_MediaCodec__BufferInfo.field_flags, value); } ......
非常简单,JNI调用 Get<Type>Field或Set<Type>Field方法获取或设置Java的字段。
至此我们就分析完了 ijkplayer 对 JNI的使用。后面附上 Signature 的说明。
Signature
在JNI中Signature主要用于操作Java类中的方法。Signature一般由两部分组成:方法参数;方法返回值。
方法参数包含在“()”中,返回值在括号外。
方法参数个数较多时会依次以“;”隔开。
当参数或者返回值是基本数据类型时,必须用其在JNI中的描述符表示。下表就是Java基本数据类型对应的JNI中的描述符。
Java类型 | 符号 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
doubl | D |
void | V |
方法参数或者返回值为java中的对象时,必须以“L”加上其路径,不过路径必须以“/”分开,自定义的对象也使用本规则,不在包中时直接“L”加上类名称。
当参数或者返回值为数组时,前面必须加上“[”。
以上就是Signature表示方法的规则!
看看下面一些Signature,你能一个个转换为相应的方法吗?
([LStudent;)[LStudent;
([I[Ljava/lang/String;[LStudent;)Ljava/lang/Object;
([LStudent;[LStudent;)[LStudent;
([Ljava/util/Iterator;)[Ljava/util/Enumeration;
([Ljava/lang/Object;)[Ljava/lang/Object;
([Ljava/lang/String;)[Ljava/lang/String;
(LStudent;)LStudent;
小结
本篇文章介绍了ijkplayer是如何使用JNI的,主要包以下几点内容:
Java 如何调用 C/C++ 接口。
C/C++ 如何调用 Java 方法。
C/C++ 如何设置/获取 Java 字段的值。
希望本文能对您有所帮助,并请多多观注。谢谢!
参考
相关推荐
- Mac右键菜单如何设置?_mac 右键菜单
-
Mac的用户都知道,Mac和Windows很大的区别在于,Windows可以使用鼠标右键完成的很多快捷操作,例如右键剪切、右键新建文件、右键快速访问等等。在工作学习中,这种快捷操作会大大提高我们的工作...
- Office局部加密隐藏信息_office隐藏修改痕迹
-
除了日常使用的图片、音视频文件外,我们还常常和别人共享使用一些Word或Excel办公文件。而这些文件中的部分内容,也许是我们不希望别人看到的。这时,就需要对Word文档或Excel表格中的部分数据实...
- 不常见但100%好用的电脑快捷键_最全的电脑快捷键
-
办公人士或者经常使用电脑的人已经熟悉了常见的Ctrl+C/V等快捷键,想要更高效、专业的操作技巧来提升工作效率。接下来,同创双子双子IT运维工程师帮忙整理了一些不太常见但非常有用的快捷键。比如Alt+...
- U盘文件被隐藏怎么恢复 U盘文件恢复隐藏的方法
-
U盘文件被隐藏怎么恢复?U盘文件被隐藏其实这是中了一种U盘病毒,它会恶意的将U盘中的文件夹隐藏起来,采用常规的方法,都无法打开查看。这种U盘病毒会把文件夹的属性给篡改掉,文件设置被改成只读、隐藏,在文...
- win7查看隐藏的文件怎么操作 win7如何打开隐藏文件
-
win7查看隐藏的文件怎么操作?在使用电脑时,有一些用户会将文件设置为隐藏属性,以保护隐私。此外,还有一些系统文件默认处于隐藏状态。那么,如何操作才能打开这些隐藏文件呢?小编今天在这就为大家分享一下w...
- 系统小技巧:八个实用设置 藏于桌面右键
-
我们除了在桌面上执行鼠标单、双击操作外,常常也会用右键菜单命令查看文件或显示属性等。其实,除此之外,桌面里还隐藏着不少可被我们利用的实用右键操作项目。下面的这些Windows10桌面右键操作技巧,不...
- 移动硬盘中的隐藏文件如何恢复显示?可尝试这些方法
-
在使用移动硬盘的过程中,有时我们可能会遇到一些文件突然变得不可见或“隐藏”的情况。这种情况可能是由于多种原因造成的,如文件系统错误、病毒感染或误操作等。面对隐藏的文件,许多用户可能会感到困惑和不知所措...
- Win11怎么查看隐藏文件和文件夹?_如何查看windows隐藏文件夹
-
一般来说系统会对比较重要的文件和文件夹添加隐藏属性,很多朋友可能找半天都找不到,那么怎么找出这些隐藏文件和文件夹呢,今天系统之家小编来教大家Win11显示隐藏文件的设置方法,操作步骤其实挺简单的,希望...
- 隐藏电脑文件(夹)竟如此简单!再也不怕被偷窥了
-
导读:谁的电脑硬盘中还不存有一些珍藏多年的学习资料,可为了保护它们各位学习爱好者也真的是煞费苦心,不管是层层文件夹“套娃”隐藏也好,修改文件名甚至修改后缀名也好,效果都是非常差的,本期文章小君就聊一聊...
- 怎么打开隐藏文件夹?_文件夹怎么弄
-
有时在电脑上,我们会发现之前的文件或者文件夹不见了,很多人会觉得会不会是误删了文件之类的。其实还有一种可能,就是你的文件或者文件夹被隐藏起来了。怎么打开隐藏文件夹?一、文件或者文件夹被隐藏的原因隐藏是...
- 移动硬盘上的隐藏文件怎么能找出来
-
移动硬盘作为一种小巧而便携式的硬盘存储器,具备容量大、兼容性好、即插即用等优势,被广泛应用于办公和家庭生活中,那么在使用移动硬盘过程中,你是否遇到了文件被隐藏的问题呢?本文将介绍恢复移动硬盘隐藏文件的...
- 系统小技巧:“发送到”菜单问题巧解决
-
此前,我们已经通过本刊的一些文章熟悉了通过手动或软件的方法定制“发送到”菜单的基本方法。在使用“发送到”菜单的过程中,还可能会遇到一些问题。例如:“发送到”菜单越用越长,能不能在不编辑删减的情况下,调...
- WIN 10系统介绍(21) 重要文件的隐藏 私密文件的保护 显示隐藏文件
-
大家好,今天介绍电脑中重要文件和文件夹的隐藏以及显示。在每个人使用的电脑中,都有一些个人的私密的资料,比如一些账目,或者个人的照片,信件,技术文档,视频等等的一些资料。我们可能不希望所有登录的用户,都...
- 状态栏在哪?手机+电脑+软件里的它 一篇讲清位置和功能
-
状态栏就是设备或软件里显示状态信息的区域,能让你快速知道时间、电量等情况。下面告诉你不同设备和软件里状态栏在哪儿、有啥用。手机上的状态栏位置:屏幕最顶端的窄条,不管用什么APP,基本都在这儿。显示...
- excel隐藏的部分内容如何显示出来?3个方法帮助你!
-
excel隐藏的部分如何显示出来?你是否曾经在Excel中遇到过某些单元格被隐藏,或者某些数据在编辑时突然消失,让你感到困惑和无助?不要担心,今天我将向你揭示如何解决这些问题,让你轻松显示隐藏的部分。...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 如何绘制折线图 (52)
- javaabstract (48)
- 新浪微博头像 (53)
- grub4dos (66)
- s扫描器 (51)
- httpfile dll (48)
- ps实例教程 (55)
- taskmgr (51)
- s spline (61)
- vnc远程控制 (47)
- 数据丢失 (47)
- wbem (57)
- flac文件 (72)
- 网页制作基础教程 (53)
- 镜像文件刻录 (61)
- ug5 0软件免费下载 (78)
- debian下载 (53)
- ubuntu10 04 (60)
- web qq登录 (59)
- 笔记本变成无线路由 (52)
- flash player 11 4 (50)
- 右键菜单清理 (78)
- cuteftp 注册码 (57)
- ospf协议 (53)
- ms17 010 下载 (60)