vlc-for-android的DNK开发简介

对于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 (MEDIALIBRARYJNIDIR) (LOCAL_PATH)/loader
LOCAL_CFLAGS := -std=c11
include $(BUILD_STATIC_LIBRARY)

libvlc dynamic library

include (CLEARVARS)LOCALMODULE:=vlcjniLOCALSRCFILES:=libvlcjnimodules.clibvlcjnisymbols.cLOCALLDFLAGS:=L (VLC_CONTRIB)/lib
LOCAL_LDLIBS := \
(VLCMODULES)  (VLC_BUILD_DIR)/lib/.libs/libvlc.a \
(VLCBUILDDIR)/src/.libs/libvlccore.a  (VLC_BUILD_DIR)/compat/.libs/libcompat.a \
(VLCCONTRIBLDFLAGS) ldllzlmllog lliveMedialUsageEnvironmentlBasicUsageEnvironmentlgroupsock la52ljpeg lavcodeclebml llua lgcryptlgpgerror  (MEDIALIBRARY_LDLIBS) \
(VLCLDFLAGS) llogLOCALWHOLESTATICLIBRARIES:=libvlcjnistaticifeq( (BUILD_ML), 1)
LOCAL_WHOLE_STATIC_LIBRARIES += libmla
endif
include (BUILDSHAREDLIBRARY)ifeq( (BUILD_ML), 1)
JNILOADER_INCLUDES := (LOCALPATH)/loader (call import-add-path, (MEDIALIBRARYJNIDIR)) (call import-module, .)
endif
二、解析内容:

1、它编译两个库,静态库和动态库。静态库对应的名字是vlcjni_static动态库对应的名字是vlcjni,同时我们知道动态会使用编译好的静态库。我 们上面使用的是动态库。
2、动态库编译的源文件是:libvlcjni-modules.c libvlcjni-symbols.c
3、传递给动态库编译器的额外参数:LOCAL_LDFLAGS := -L(VLCCONTRIB)/lib4LOCALLDLIBS:=  (VLC_MODULES) \
(VLCBUILDDIR)/lib/.libs/libvlc.a  (VLC_BUILD_DIR)/src/.libs/libvlccore.a \
(VLCBUILDDIR)/compat/.libs/libcompat.a  (VLC_CONTRIB_LDFLAGS) \
-ldl -lz -lm -llog \
-lliveMedia -lUsageEnvironment -lBasicUsageEnvironment -lgroupsock \
-la52 -ljpeg \
-lavcodec -lebml \
-llua \
-lgcrypt -lgpg-error \
(MEDIALIBRARYLDLIBS)  (VLC_LDFLAGS) \
-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 (BUILDSHAREDLIBRARY)LOCALPATH:= (call my-dir)
每个Android.mk文件必须以定义LOCAL_PATH为开始。它用于在开发tree中查找源文件。
宏my-dir 则由Build System提供。返回包含Android.mk的目录路径。
include (CLEARVARS)CLEARVARSBuildSystemGNUMakefileLOCALxxx.LOCALMODULE,LOCALSRCFILES,LOCALSTATICLIBRARIESLOCALPATH.GNUMakeLOCALMODULE:=hellojniLOCALMODULEAndroid.mkBuildSystemfoolibfoo.so.libfoo.libfoo.so.LOCALSRCFILES:=hellojni.cLOCALSRCFILESC/C++buildSystemC++.cpp.LOCALCPPEXTENSIONinclude (BUILD_SHARED_LIBRARY)
BUILD_SHARED_LIBRARY:是Build System提供的一个变量,指向一个GNU Makefile Script。
它负责收集自从上次调用 include $(CLEAR_VARS) 后的所有LOCAL_XXX信息。并决定编译为什么。

BUILD_STATIC_LIBRARY:编译为静态库。
BUILD_SHARED_LIBRARY :编译为动态库
BUILD_EXECUTABLE:编译为Native C可执行程序

  1. 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.2BUILDSHAREDLIBRARYinclude (CLEAR_VARS) 后的所有LOCAL_XXX信息。
    并决定如何将你列出的Source编译成一个动态库。 注意,在包含此文件前,至少应该包含:LOCAL_MODULE and LOCAL_SRC_FILES 例如:
    include (BUILDSHAREDLIBRARY)2.1.3BUILDSTATICLIBRARYinclude (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.5PREBUILTSHAREDLIBRARYbuildBUILDSHAREDLIBRARYandBUILDSTATICLIBRARYLOCALSRCFILESsourcefile.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 提供的功能宏,只有通过类似: (callfunction)2.2.1:mydir: (call my-dir):
返回最近一次include的Makefile的路径。通常返回Android.mk所在的路径。它用来作为Android.mk的开头来定义LOCAL_PATH.
LOCAL_PATH := (callmydir)includeMakefileIncludeMakefile (call my-dir)会返回其它Android.mk 所在路径。 例如:
LOCAL_PATH := (callmydir)...declareonemoduleinclude (LOCAL_PATH)/foo/Android.mk LOCAL_PATH := (callmydir)declareanothermoduleLOCALPATH  PATH/foo。 而非PATH.2.2.2:allsubdirmakefiles:mydirAndroid.mksources/foo/Android.mksources/foo/lib1/Android.mksources/foo/lib2/Android.mkIfsources/foo/Android.mkinclude (call all-subdir-makefiles) 那则自动include 了sources/foo/lib1/Android.mk and sources/foo/lib2/Android.mk。

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寻找指定的模块名。
(callimportmodule,)2.3:BuildSysteminclude (CLEAR_VARS)' 和 'include (BUILDXXXXX)include (CLEAR_VARS) script用来清空这些变量。
include $(BUILD_XXXXX)收集和使用这些变量。

2.3.1: LOCAL_PATH:
这个值用来给定当前目录。必须在Android.mk的开是位置定义之。
例如: LOCAL_PATH := (callmydir)LOCALPATHinclude (CLEAR_VARS) 清理。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章