对于VLC-Android的JNI设计,我们从java层开始,一步步分析。
libvlc.java文件解读
libvlc.java解读(libvlc\src\org\videolan\libvlc\libvlc.java)
一、对java暴露的接口类是LibVLC:
通过它,我们创建一个播放器实例。
public class LibVLC extends VLCObject
libvlc jni static library
include (CLEAR_VARS)
LOCAL_MODULE := vlcjni_static
LOCAL_SRC_FILES := libvlcjni.c
LOCAL_SRC_FILES += libvlcjni-mediaplayer.c
LOCAL_SRC_FILES += libvlcjni-vlcobject.c
LOCAL_SRC_FILES += libvlcjni-media.c libvlcjni-medialist.c libvlcjni-mediadiscoverer.c
LOCAL_SRC_FILES += libvlcjni-dialog.c
LOCAL_SRC_FILES += thumbnailer.c
LOCAL_SRC_FILES += std_logger.c
LOCAL_C_INCLUDES := (VLC_SRC_DIR)/include
LOCAL_CFLAGS := -std=c11
include $(BUILD_STATIC_LIBRARY)
libvlc dynamic library
include
LOCAL_LDLIBS := \
LOCAL_WHOLE_STATIC_LIBRARIES += libmla
endif
include
JNILOADER_INCLUDES :=
endif
二、解析内容:
1、它编译两个库,静态库和动态库。静态库对应的名字是vlcjni_static动态库对应的名字是vlcjni,同时我们知道动态会使用编译好的静态库。我 们上面使用的是动态库。
2、动态库编译的源文件是:libvlcjni-modules.c libvlcjni-symbols.c
3、传递给动态库编译器的额外参数:LOCAL_LDFLAGS := -L
-ldl -lz -lm -llog \
-lliveMedia -lUsageEnvironment -lBasicUsageEnvironment -lgroupsock \
-la52 -ljpeg \
-lavcodec -lebml \
-llua \
-lgcrypt -lgpg-error \
-llog
libvlcjni.c文件解读
libvlcjin.c文件解读(libvlc\jni\ilbvlcjin.c)
一、通过源码可以知道,它是采用静态注册方法:由虚拟机完成两者的匹配。例如:java层调用String version()方法时,它会自动到jni库中寻找jstring Java_org_videolan_libvlc_LibVLC_version()函数,如果找到,则为这两个函数建立关联关系,以后调用String version()时直接找建立好关系的JNI层对应的函数指针即可。
二、在jstring Java_org_videolan_libvlc_LibVLC_version()中调用函数libvlc_get_version(),libvlc_get_version()函数是在vlc\lib\core.c中定义。
jstring Java_org_videolan_libvlc_LibVLC_version(JNIEnv* env, jobject thiz)
{
return (*env)->NewStringUTF(env, libvlc_get_version());
}
三、通过上面的分析可以知道,ndk层的关系简单的理解:在libvlcjin.c中封装需要的功能函数,在libvlc.java中定义本地方法和对本地方法的封装。
四、源码分析
源码主题结构如下:
/* 当一个附属于java虚拟机的线程取消或者退出时,会调用这个函数*/
static void jni_detach_thread(void *data)
给外部模块获取环境变量函数
JNIEnv *jni_get_env(const char *name)
给本库加载java层类的属性和方法ID。
int VLCJNI_OnLoad(JavaVM vm, JNIEnv env)
void VLCJNI_OnUnload(JavaVM *vm, JNIEnv *env)
一下是libvlc.java对应的本地方法:
void Java_org_videolan_libvlc_LibVLC_nativeNew(JNIEnv *env, jobject thiz,jobjectArray jstringArray,jstring jhomePath)
void Java_org_videolan_libvlc_LibVLC_nativeRelease(JNIEnv *env, jobject thiz)
jstring Java_org_videolan_libvlc_LibVLC_version(JNIEnv* env, jobject thiz)
jstring Java_org_videolan_libvlc_LibVLC_compiler(JNIEnv* env, jobject thiz)
jstring Java_org_videolan_libvlc_LibVLC_changeset(JNIEnv* env, jobject thiz)
void Java_org_videolan_libvlc_LibVLC_nativeSetUserAgent(JNIEnv* env,jobject thiz,jstring jname,jstring jhttp)
五、VLCJNI_OnLoad函数分析:
int VLCJNI_OnLoad(JavaVM vm, JNIEnv env)
{
myVm = vm;
/* Create a TSD area and setup a destroy callback when a thread that
* previously set the jni_env_key is canceled or exited */
创建一个TSD内存区并在一个线程取消或退出时设置jni_env_key回调预先设置。
因为vlc是一个多线程的播放器,这是初始化线程的传递数据的方法
if (pthread_key_create(&jni_env_key, jni_detach_thread) != 0)
return -1;
jclass Version_clazz;
jfieldID SDK_INT_fieldID;
各种GET_CLASS、GET_ID调用,主要是引用各个模块
GET_CLASS(Version_clazz, “android/os/Build$VERSION”, false);
GET_ID(GetStaticFieldID, SDK_INT_fieldID, Version_clazz, “SDK_INT”, “I”);
fields.SDK_INT = (*env)->GetStaticIntField(env, Version_clazz,
SDK_INT_fieldID);
return 0;
}
看他对应的卸载函数VLCJNI_OnUnload主要是释放引用*env)->DeleteGlobalRef(env, fields.IllegalStateException.clazz);
知道VLCJNI_OnLoad函数主要功能就是加载引用。
我们跟踪动态库的源文件libvlcjni-modules.c libvlcjni-symbols.c,
打开两个文件会发现,这个C文件是一些函数指针,指向执行函数:vlc_static_modules函数指针数组包含的加载模块的入口函数。libvlc_functions函数指针数组包含的是各模块的功能函数。
int vlc_entry__packetizer_h264 (int ()(void , void , int, …), void );
…………………………
…………………………
const void *vlc_static_modules[] = {
vlc_entry__packetizer_h264,
……………
}
下是libvlcjni-symbols.c文件
int libvlc_add_intf(void);
………….
const void *libvlc_functions[] = {
libvlc_errmsg,
…….
}
小结一下
上面的分析,我们知道java层定义本地方法后调用动态库。虚拟机自动调用jni_onload函数,此时java层调用的
System.loadLibrary(“vlcjni”);
加载vlcjni动态库中的jni_onload函数,
java层调用
System.loadLibrary(“jniloader”);
应该加载jniloader动态库的jni_onload函数。
现在我们知道了,vlcjni.so库对应的是没有jni_onload函数的。调用vlcjni.so库时会直接调用到源码vlc\src\android.c内定义的jni_onload函数的。java层的环境变量是通过封装的库jniloader.so传递过去的。
回头看java层,我们发现源文件目录下有许多的类:
其中只有LibVLC调用了System.loadLibrary();其它的类中只有native方法却没有System.loadLibrary()。其它类的属性和方法是通过库jniloader.so调用jni_onload中加载库vlcjni.so中的VLCJNI_OnLoad函数和MediaLibraryJNI_OnLoad函数,将我们获取到类的属性和方法ID保存在全局变量结构体fields中。
我们知道VLC播放器必须要一个实例做为句柄,所有的操作都是围绕这个实例。我们可以看到在jni\libvlcjni.c中的对应的本地方法:
void Java_org_videolan_libvlc_LibVLC_nativeNew(JNIEnv *env, jobject thiz,
jobjectArray jstringArray,
jstring jhomePath)
{
vlcjni_object *p_obj = NULL;
libvlc_instance_t *p_libvlc = NULL;
jstring *strings = NULL;
const char **argv = NULL;
int argc = 0;
忽略部分解析参数
创建一个播放器实例
p_libvlc = libvlc_new(argc, argv);
创建一个vlcobject,这个用于java层封装vlc播放器,在各个模块功能中传递播放器实例
p_obj = VLCJniObject_newFromLibVlc(env, thiz, NULL);
将新建的播放器实例保存在vlcobject中
p_obj->u.p_libvlc = p_libvlc;
}
现在我们编写ndk的主要步骤已经清楚:
1、在java层定义自己需要的类和本地方法。
2、我们定义的类和方法对应的C语言编写在libvlc\Jni下,则在此目录下的Utils.h的fields中定义结构保存类的属性和方法ID。
(括号内是在其它库中的操作方法思路与此相同,下面步骤中不在同样的如果定义的类和方法对应的C语言编写在对应的medialibrary\jni\目录下,则在此目录下的medialibrary\jni\Utils.h中定义结构体保存类的属性和本地方法ID。)
我们也可以定义自己的库来管理VLC内核。不过此时需要在java层来加载库。
3、我们在libvlc\jni\libvlcjni.c中的VLCJNI_OnLoad函数中添加类的属性和本地方法ID。
4、做好上面的准备工作之后就可以编写自己的C语言库函数。比如定义为libvlcjni-hello.c
5、在libvlcjni-hello.c中的编写方法如下:
a、定义自己需要的数据结构。
b、通过VLCJniObject_getInstance()函数回调java层中的vlcjni_object。
c、如此我们便可以操作java层定义的播放器实例了。可以调用vlc封装好的函数,也可以是自己封装的函数。
上面说的是比较大的改动,如果我们仅仅需要某些方法即可,则可以把本地方法封装在java层对应的类中,在对应的Utils.h中fields变量中添加此类对应的方法ID变量。如果仅仅定义一个不在其它类中,也可以在libvlc\jni\libvlcjni.c中定义对应的函数。
6、如果我们编写的程序新增源文件也需要在Android.mk中添加相应源文件。
按照上述方法编写程序后,我们重新编译即可。当然我们修改的比较深入,比如我们需要填一个全新的模块时,编写模块和添加到库中的方法见下一篇文章。
附录方法
Android.mk语法:
转载:https://www.cnblogs.com/wainiwann/p/3837936.html
0. Android.mk简介:
Android.mk文件用来告知NDK Build 系统关于Source的信息。 Android.mk将是GNU Makefile的一部分,且将被Build System解析一次或多次。
所以,请尽量少的在Android.mk中声明变量,也不要假定任何东西不会在解析过程中定义。
Android.mk文件语法允许我们将Source打包成一个”modules”. modules可以是:
静态库
动态库。
只有动态库可以被 install/copy到应用程序包(APK). 静态库则可以被链接入动态库。
可以在一个Android.mk中定义一个或多个modules. 也可以将同一份source 加进多个modules.
Build System帮我们处理了很多细节而不需要我们再关心。例如:你不需要在Android.mk中列出头文件和外部依赖文件。
NDK Build System自动帮我们提供这些信息。这也意味着,当用户升级NDK后,你将可以受益于新的toolchain/platform而不必再去修改Android.mk.
1. Android.mk语法:
首先看一个最简单的Android.mk的例子:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include
每个Android.mk文件必须以定义LOCAL_PATH为开始。它用于在开发tree中查找源文件。
宏my-dir 则由Build System提供。返回包含Android.mk的目录路径。
include
BUILD_SHARED_LIBRARY:是Build System提供的一个变量,指向一个GNU Makefile Script。
它负责收集自从上次调用 include $(CLEAR_VARS) 后的所有LOCAL_XXX信息。并决定编译为什么。
BUILD_STATIC_LIBRARY:编译为静态库。
BUILD_SHARED_LIBRARY :编译为动态库
BUILD_EXECUTABLE:编译为Native C可执行程序
- NDK Build System变量:
NDK Build System 保留以下变量名:
以LOCAL_ 为开头的
以PRIVATE_ ,NDK_ 或者APP_ 开头的名字。
小写字母名字:如my-dir
如果想要定义自己在Android.mk中使用的变量名,建议添加 MY_ 前缀。
2.1: NDK提供的变量:
此类GNU Make变量是NDK Build System在解析Android.mk之前就定义好了的。
2.1.1:CLEAR_VARS:
指向一个编译脚本。必须在新模块前包含之。
include(CLEARVARS)2.1.2:BUILDSHAREDLIBRARY:指向一个编译脚本,它收集自从上次调用include (CLEAR_VARS) 后的所有LOCAL_XXX信息。
并决定如何将你列出的Source编译成一个动态库。 注意,在包含此文件前,至少应该包含:LOCAL_MODULE and LOCAL_SRC_FILES 例如:
include(BUILDSHAREDLIBRARY)2.1.3:BUILDSTATICLIBRARY:与前面类似,它也指向一个编译脚本,收集自从上次调用include (CLEAR_VARS) 后的所有LOCAL_XXX信息。
并决定如何将你列出的Source编译成一个静态库。 静态库不能够加入到Project 或者APK中。但它可以用来生成动态库。
LOCAL_STATIC_LIBRARIES and LOCAL_WHOLE_STATIC_LIBRARIES将描述之。
include(BUILDSTATICLIBRARY)2.1.4:BUILDEXECUTABLE:与前面类似,它也指向一个编译脚本,收集自从上次调用include (CLEAR_VARS) 后的所有LOCAL_XXX信息。
并决定如何将你列出的Source编译成一个可执行Native程序。
include(BUILDEXECUTABLE)2.1.5:PREBUILTSHAREDLIBRARY:把这个共享库声明为“一个”独立的模块。指向一个build脚本,用来指定一个预先编译好多动态库。与BUILDSHAREDLIBRARYandBUILDSTATICLIBRARY不同,此时模块的LOCALSRCFILES应该被指定为一个预先编译好的动态库,而非sourcefile.LOCALPATH:= (call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := foo-prebuilt # 模块名
LOCAL_SRC_FILES := libfoo.so # 模块的文件路径(相对于 LOCAL_PATH)
include (PREBUILT_SHARED_LIBRARY) # 注意这里不是 BUILD_SHARED_LIBRARY
这个共享库将被拷贝到 PROJECT/obj/local 和 $PROJECT/libs/ (stripped) 主要是用在将已经编译好的第三方库
使用在本Android Project中。为什么不直接将其COPY到libs/armabi目录呢?因为这样做缺陷很多。下一节再详细说明。
2.1.6: PREBUILT_STATIC_LIBRARY: 预先编译的静态库。 同上。
2.1.7: TARGET_ARCH: 目标CPU架构名。如果为 “arm” 则声称ARM兼容的指令。与CPU架构版本无关。
2.1.8: TARGET_PLATFORM: 目标平台的名字。
2.1.9:TARGET_ARCH_ABI
Name of the target CPU+ABI
armeabi For ARMv5TE armeabi-v7a
2.1.10:TARGET_ABI
2.2: NDK提供的功能宏:
GNU Make 提供的功能宏,只有通过类似:
返回最近一次include的Makefile的路径。通常返回Android.mk所在的路径。它用来作为Android.mk的开头来定义LOCAL_PATH.
LOCAL_PATH :=
LOCAL_PATH :=
2.2.3:this-makefile:
当前Makefile的路径。
2.2.4:parent-makefile:
返回include tree中父Makefile 路径。 也就是include 当前Makefile的Makefile Path。
2.2.5:import-module:
允许寻找并inport其它modules到本Android.mk中来。 它会从NDK_MODULE_PATH寻找指定的模块名。
include $(BUILD_XXXXX)收集和使用这些变量。
2.3.1: LOCAL_PATH:
这个值用来给定当前目录。必须在Android.mk的开是位置定义之。
例如: LOCAL_PATH :=
2.3.2: LOCAL_MODULE:
modules名。在include $(BUILD_XXXXX)之前,必须定义这个变量。此变量必须唯一且不能有空格。
通常,由此变量名决定最终生成的目标文件名。
2.3.3: LOCAL_MODULE_FILENAME:
可选。用来override LOCAL_MODULE. 即允许用户重新定义最终生成的目标文件名。
LOCAL_MODULE := foo-version-1 LOCAL_MODULE_FILENAME := libfoo
2.3.4:LOCAL_SRC_FILES:
为Build Modules而提供的Source 文件列表。不需要列出依赖文件。 注意:文件相对于LOCAL_PATH存放,
且可以提供相对路径。 例如:
LOCAL_SRC_FILES := foo.c \ toto/bar.c
2.3.5: LOCAL_CPP_EXTENSION:
指出C++ 扩展名。(可选)
LOCAL_CPP_EXTENSION := .cxx 从NDK R7后,可以写多个:
LOCAL_CPP_EXTENSION := .cxx .cpp .cc
2.3.6:LOCAL_CPP_FEATURES:
可选。用来指定C++ features。
LOCAL_CPP_FEATURES := rtti
LOCAL_CPP_FEATURES := exceptions
2.3.7:LOCAL_C_INCLUDES:
一个可选的path列表。相对于NDK ROOT 目录。编译时,将会把这些目录附上。
LOCAL_C_INCLUDES := sources/foo LOCAL_C_INCLUDES := $(LOCAL_PATH)/../foo
2.3.8: LOCAL_CFLAGS:
一个可选的设置,在编译C/C++ source 时添加如Flags。
用来附加编译选项。 注意:不要尝试在此处修改编译的优化选项和Debug等级。它会通过您Application.mk中的信息自动指定。
也可以指定include 目录通过:LOCAL_CFLAGS += -I。 这个方法比使用LOCAL_C_INCLUDES要好。因为这样也可以被ndk-debug使用。
2.3.9: LOCAL_CXXFLAGS:
LOCAL_CPPFLAGS的别名。
2.3.10: LOCAL_CPPFLAGS:
C++ Source 编译时添加的C Flags。这些Flags将出现在LOCAL_CFLAGS flags 的后面。
2.3.11: LOCAL_STATIC_LIBRARIES:
要链接到本模块的静态库list。(built with BUILD_STATIC_LIBRARY)
2.3.12: LOCAL_SHARED_LIBRARIES:
要链接到本模块的动态库。
2.3.13:LOCAL_WHOLE_STATIC_LIBRARIES:
静态库全链接。 不同于LOCAL_STATIC_LIBRARIES,类似于使用–whole-archive
2.3.14:LOCAL_LDLIBS:
linker flags。 可以用它来添加系统库。 如 -lz:
LOCAL_LDLIBS := -lz
2.3.15: LOCAL_ALLOW_UNDEFINED_SYMBOLS:
2.3.16: LOCAL_ARM_MODE:
缺省模式下,ARM目标代码被编译为thumb模式。每个指令16位。如果指定此变量为:arm。 则指令为32位。
LOCAL_ARM_MODE := arm 其实也可以指定某一个或者某几个文件的ARM指令模式。
2.3.17: LOCAL_ARM_NEON:
设置为true时,会讲浮点编译成neon指令。这会极大地加快浮点运算(前提是硬件支持)
只有targeting 为 ‘armeabi-v7a’时才可以。
2.3.18:LOCAL_DISABLE_NO_EXECUTE:
2.3.19: LOCAL_EXPORT_CFLAGS:
定义这个变量用来记录C/C++编译器标志集合,
并且会被添加到其他任何以LOCAL_STATIC_LIBRARIES和LOCAL_SHARED_LIBRARIES的模块的LOCAL_CFLAGS定义中
LOCAL_SRC_FILES := foo.c bar.c.arm
注意:此处NDK版本为NDK R7C.(不同NDK版本,ndk-build所产生的Makefile并不完全相同)
动态库和静态库的特点:
静态库:
静态库是obj文件的一个集合,通常静态库以”.a”为后缀。静态库由程序ar生成。
2.静态库的优点是可以在不用重新编译程序库代码的情况下,进行程序的重新链接,这种方法节省了编译过程的时间(在编译大型程序的时候,需要花费很长的时间)。静态库的另一个优点是开发者可以提供库文件给使用的人员,不用开放源代码,这是库函数提供者经常采用的手段。
动态库:
1.动态链接库是程序运行时加载的库,当动态链接库正确安装后,所有的程序都可以使用动态库来运行程序。动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址是相对地址,不是绝对地址,其真实地址在调用动态库的程序加载时形成。
2.动态链接库的名称有别名(soname), 真名(realname)和链接名(linker name)。别名由一个前缀lib,然后是库的名字,再加上一个后缀“.so”构成。真名是动态链接库真实名称,一般总是在别名的基础加上一个小版本号,发布版本等构成。除此之外,还有一个链接名,即程序链接时使用的库的名字。
3.在动态链接库安装的时候,总是复制文件到某个目录下,然后用一个软连接生成别名,在库文件进行更新的时候,仅仅更新软链接即可。
来源: http://topmanopensource.iteye.com/blog/1752490