编译系统分析
1.configure
pjsip是基于autoconf与automak的编译系统,首先用./configure 传入参数,然后传入build.mak.in,最后生成build.mak,比如,我们用./configure --enable_shared,传入一个enable_shared参数,在aconfigure.ac中,有如下一段:
AC_SUBST(ac_shared_libraries) AC_ARG_ENABLE(shared, AC_HELP_STRING([--enable-shared], [Build shared libraries]), [if test "$enable_shared" = "yes"; then [ac_shared_libraries=1] CFLAGS="$CFLAGS -fPIC" AC_MSG_RESULT([Building shared libraries... yes]) fi], AC_MSG_RESULT([Building shared libraries... no]) ) |
其中如果检测到了enable_shared=yes,那么就会将ac_shared_libraries=1,这个变量我们从build.mak.in中可以看到。
ifeq (@ac_shared_libraries@,1) export PJ_SHARED_LIBRARIES := 1 endif |
它其实是控制build.mak中是否输出PJ_SHARED_LIBRARIES这个变量,如果没有定义,那么最终build.mak中生成的是
ifeq (,1) export PJ_SHARED_LIBRARIES := 1 endif |
2.目标编译及rules.mak
如果我们没有定义上面的enable_shared参数,那么最终我们为每个模块生成的,是一个静态库.a文件。顶层Makefile进入,会进入每个模块目录下执行make
all clean dep depend print: for dir in $(DIRS); do \ if $(MAKE) $(MAKE_FLAGS) -C $$dir $@; then \ true; \ else \ exit 1; \ fi; \ done |
因此,我们分析一个模块的编译过程就够了,以pjlib为例。
其实对于每个模块比如pjlib,在pjlib/build目录下都有一个Makefile文件,对于每个模块的编译流程其实都是千篇一律的,比如收集objs目标文件,建立依赖,然后编译.o,最终打包成一个静态库,从这个流程中我们知道除了收集objs目标和一些特定的编译参数之外,其他的过程都一样,因此,这一块代码,被pjsip独立出来,放在了build/rules.mak中。我们分析一下pjlib的makefile.
1.包含rules.mak,导出最终目标给rules.mak
RULES_MAK := $(PJDIR)/build/rules.mak
export PJLIB_LIB := libpj-$(TARGET_NAME)$(LIBEXT) |
2.收集OBJS,定义编译选项,并全部导出,不导出rules.mak看不到哦
export PJLIB_SRCDIR = ../src/pj export PJLIB_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ activesock.o array.o config.o ctype.o errno.o except.o fifobuf.o \ guid.o hash.o ip_helper_generic.o list.o lock.o log.o os_time_common.o \ os_info.o pool.o pool_buf.o pool_caching.o pool_dbg.o rand.o \ rbtree.o sock_common.o sock_qos_common.o sock_qos_bsd.o \ ssl_sock_common.o ssl_sock_ossl.o ssl_sock_dump.o \ string.o timer.o types.o export PJLIB_CFLAGS += $(_CFLAGS) export PJLIB_CXXFLAGS += $(_CXXFLAGS) export PJLIB_LDFLAGS += $(_LDFLAGS) |
3.将实际编译任务交给rules.mak
$(PJLIB_LIB) $(PJLIB_SONAME): $(MAKE) -f $(RULES_MAK) APP=PJLIB app=pjlib $(subst /,$(HOST_PSEP),$(LIBDIR)/$@) |
可以看到,我们传入了两个参数APP以及app,这就是rules.mak可以复用于编译所有模块的原因,另外在最后我们指定了实际的编译目标,就是$(LIBDIR)/$@
这个目标其实就是PJLIB_LIB,libpj-$(TARGET_NAME)$(LIBEXT),这个TARGET_NAME也是可以在configure指定的,可以是win32,cygw之类的,如果没有指定就是auto自动识别,在build.mak中可以看到是arm-unknown-linux-androideabi,所以对于pjlib,我们最终生成的静态库目标是../lib/libpj-arm-unknown-linux-androideabi.a
4.rules.mak编译过程
ifneq ($(LIB),) $(subst /,$(HOST_PSEP),$(LIBDIR)/$(LIB)): $(OBJDIRS) $(OBJS) $($(APP)_EXTRA_DEP) if test ! -d $(LIBDIR); then $(subst @@,$(subst /,$(HOST_PSEP),$(LIBDIR)),$(HOST_MKDIR)); fi $(AR) $(AR_FLAGS) $@ $(OBJS) $(RANLIB) $@ endif |
很方便就可以在里面找到,我们实际传入的目标,里面的依赖$(objs)就是我们导出的,因此它会自动定位到下面的编译目标。
$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.c $(CC) $($(APP)_CFLAGS) \ $(CC_OUT)$(subst /,$(HOST_PSEP),$@) \ $(subst /,$(HOST_PSEP),$<) |
编译完后最终调用AR,生成静态库。
pjsip代码很容易下,在官网用git下载就行,2.x版本已经支持video,从配置和编译上,比以前复杂多了,这里我们下下来的最新版本是2.4
1.编译前配置
配置config_site.h文件,这是个啥呢,它是对本地库的自定义配置,所有的自定义配置都必须放在这个文件中,因为这个文件并不在svn仓库中,所以随便修改不用担心在同步svn时会被冲掉。
这个config_site.h文件中可以复写以下文件中的宏:
· PJLIB Configuration (the pjlib/config.h file)
· PJLIB-UTIL Configuration (the pjlib-util/config.h file)
· PJNATH Configuration (the pjnath/config.h file)
· PJMEDIA Configuration (the pjmedia/config.h file)
· PJSIP Configuration (the pjsip/sip_config.h file)
同时,这个pjlib/include/pj/目录下已经有一个config_site_sample.h.我们先创建,然后包含这个文件就行。
在config_site.h中添加android架构以及media支持。
#define PJ_CONFIG_ANDROID 1 #include <pj/config_site_sample.h>
#define PJMEDIA_HAS_VIDEO 1 |
2.添加NDK路径支持
编译时需要用到环境变量中的NDK_PATH以及两个变量TARGET_GET和APP_PLATFORM,这里我们直接写入到配置文件configue-android中。
NDK_PATH=$(dirname $(which ndk-build)) TARGET_ABI=armeabi-v7a APP_PLATFORM=android-19 |
然后将NDK_PATH导出
export ANDROID_NDK_ROOT=${NDK_PATH} |
当然,前提是这里我们必须要先将ndk path配置到环境变量PATH中,在/etc/profile中配置
3.编译
$ cd /path/to/your/pjsip/dir |
编译完成之后,如果我们定义上面的enable-shared的话,会为每个模块生成一个.a的静态库。那编译成静态库有什么意义呢,为什么不默认编成动态库?因为对于Android这种系统来说,默认编成动态库,那么Android也无法使用,因为这样的动态库是纯C库,java层是无法调用的,因此,pjsip使用了swig,自动化的为所有导出接口生成了android使用的JNI层文件,并与前面的所有静态库一起,编译成了android平台的动态库。
4.swig部分编译
我们看一下pjsip-apps/src/swig/Makefile
如果识别为android平台,它会去java子目录执行make -C,继续看java/Makefile.
首先定义全局目标
all: $(LIBPJSUA2_SO) java
其中LIBPJSUA2_SO就是libpjsua2.so,首先就是要生成.so文件,这个文件的目标从哪来呢?
$(LIBPJSUA2_SO): $(OUT_DIR)/pjsua2_wrap.o $(PJ_CXX) -shared -o $(LIBPJSUA2_SO) $(OUT_DIR)/pjsua2_wrap.o \ $(MY_CFLAGS) $(MY_LDFLAGS) |
我们可以看到它依赖于pjsua2_wrap.o(swig生成的中间接口目标),还有MY_LDFLAGS.
MY_LDFLAGS := $(PJ_LDXXFLAGS) $(PJ_LDXXLIBS) $(MY_JNI_LDFLAGS) $(LDFLAGS) |
里面包含了一个PJ_LDXXLIBS,这个定义在build.mak中,引用的变量如下
export APP_LDLIBS := $(PJSUA_LIB_LDLIB) \ $(PJSIP_UA_LDLIB) \ $(PJSIP_SIMPLE_LDLIB) \ $(PJSIP_LDLIB) \ $(PJMEDIA_CODEC_LDLIB) \ $(PJMEDIA_LDLIB) \ $(PJMEDIA_VIDEODEV_LDLIB) \ $(PJMEDIA_AUDIODEV_LDLIB) \ $(PJMEDIA_LDLIB) \ $(PJNATH_LDLIB) \ $(PJLIB_UTIL_LDLIB) \ $(APP_THIRD_PARTY_LIBS)\ $(APP_THIRD_PARTY_EXT)\ $(PJLIB_LDLIB) \ |
从中我们可以看到,几乎所有编出来的静态库,都被它打包到一起了。
到这里,整个编译流程几乎已经确定,我们在swig目录下直接make, so文件出来了,我们可以将android工程copy出来到eclipse或者android studio下面编译了。
5.android app注册
原始demo注册很蛋疼,选项根本不知道填什么
ID : | sip:010101@58.58.32.154 |
Registrar: | sip:58.58.32.154 |
Proxy: | sip:58.58.32.154 |
Username | 010101 |
Password: | **** |