linphone 分析1 linphone的架构和初始化

1.linphone 包含的库


1 ReadLine 一个终端显示库, Linphone 会用到它时里面的事件循环机制来读取会话事件。
2 ffmpeg 音视频编解码库
3 Speex 专为通话过程设计的音频编码库
4 libtheora 视频压缩编码库
5 libfaac mpeg4 的音频编码器
6 libfaad2 AAC 音频解码器
7 SDL 简单的视频支持层
8 libosip2 SIP 的简单实现
9 libeXosip2 对 libosip2 的调用进行封装,隐藏了多媒体会话建立过程中 SIP 的细节
10 linphone-3.0 linphone 的主程序,包括 mediastream, oRtp, coreapi 以及 console 四个部分、


2.运行时候系统图为

linphone 分析1 linphone的架构和初始化_第1张图片


通话双方在通信前使用 exosip 进行会话协商。上图左边部分展示这一部分的流程。 Exosip 后台
任务完成数据的接收和发送,并通过事件队列通知 linphone 底层的状态变化。
filter 的构建在会话协商成功建立后就顺带完成了,并且 ticker 任务也跑起来了。此时按照 filter
graphics 构建的通道,音视频流不断的从硬件设备上读取,并经过编码压缩送给 RTP 会话,之后送
到对端, 对端到达的音视频流也经过 RTP 会话接收送到解码解压缩 filter, 还原出原始的音视频流交
给硬件设备播放。媒体数据在这两路流中源源不断的流动,完成了双方的可视通话。
上层 linphone 的 core 任务也不断的对底层进行迭代检查。所做的基本工作如下:
对于 sip 协议部分, core 一直等待从事件队列上拿事件。这些事件是 exosip 任务在处理 sip 消息
过程中添加到事件队列上的。每当得到新的事件后, core 就从应用层的角度出发,进行处理。
对于视频流:基本上只处理 rtcp 数据包到达的事件。 stream 上也有一个事件队列,用于保存该流
上的相关事件。对于 rtcp 数据包事件, core 也只处理 sr 类型 rtcp 包,即发送端报告,得到 jitter 和
包丢失率。如果设置了自适应比特率,则调用相关接口进行处理。此过程不断进行,直到当前事件
上的包处理完。
对于音频流,检查流是否还是活动的。通过比较 RTP stats 中接收的数据包数目是否发生变化,
如果在超时时间到达后,接收的数据量还没有发生变化,则认为音频没有响应。



3 LinphoneCore 结构体


/*
* Initialize linphone core
*/
linphonec=linphone_core_new (&linphonec_vtable, configfile_name, factory_configfile_name, NULL


//jimmy +++ linphonecore struct
struct _LinphoneCore
{
LinphoneCoreVTable vtable;
Sal *sal;  //exosip
LinphoneGlobalState state;
struct _LpConfig *config;
RtpProfile *default_profile;  //rtp payload
net_config_t net_conf;
sip_config_t sip_conf;
rtp_config_t rtp_conf;
sound_config_t sound_conf;
video_config_t video_conf;
codecs_config_t codecs_conf;
ui_config_t ui_conf;
autoreplier_config_t autoreplier_conf;
MSList *payload_types;   //rtp payload
int dyn_pt;
LinphoneProxyConfig *default_proxy;
MSList *friends;
MSList *auth_info;
struct _RingStream *ringstream;
time_t dmfs_playing_start_time;
LCCallbackObj preview_finished_cb;
LinphoneCall *current_call;   /* the current call */
MSList *calls;/* all the processed calls */
MSList *queued_calls;/* used by the autoreplier */
MSList *call_logs;
MSList *chatrooms;
int max_call_logs;
int missed_calls;
VideoPreview *previewstream;
struct _MSEventQueue *msevq;
LinphoneRtpTransportFactories *rtptf;
MSList *bl_reqs;
MSList *subscribers;/* unknown subscribers */
int minutes_away;
LinphoneOnlineStatus presence_mode;
char *alt_contact;
void *data;
char *play_file;
char *rec_file;
time_t prevtime;
int audio_bw;
LinphoneWaitingCallback wait_cb;
void *wait_ctx;
unsigned long video_window_id;
unsigned long preview_window_id;
time_t netup_time; /*time when network went reachable */
MSList *hooks;
LinphoneConference conf_ctx;
char* zrtp_secrets_cache;
LinphoneVideoPolicy video_policy;
bool_t use_files;
bool_t apply_nat_settings;
bool_t initial_subscribes_sent;
bool_t bl_refresh;

bool_t preview_finished;
bool_t auto_net_state_mon;
bool_t network_reachable;
bool_t use_preview_window;

time_t network_last_check;
bool_t network_last_status;


bool_t ringstream_autorelease;
bool_t pad[3];
int device_rotation;
int max_calls;
LinphoneTunnel *tunnel;
char* device_id;
MSList *last_recv_msg_ids;
char *chat_db_file;
#ifdef MSG_STORAGE_ENABLED
sqlite3 *db;
#endif
#ifdef BUILD_UPNP
UpnpContext *upnp;
#endif //BUILD_UPNP
};


程序中定义了一个比较大的数据结构体——linphonecore,将其作为总的控制结构。通过该结构
体,可以找到所需的大部分信息。这些信息要么是直接在该结构体中定义,要么是在其包含的模块
相关的子结构体中。可以想象,内存中保存的该结构体,就像整体软件信息的总控点,通过该总控
点,可以直接或者间接的得到系统相关的信息,这在一定程度上可以简化系统的架构和实现。(在许
多开源软件中都可以看到这种代码架构方式。)


指针 sal 指向 sal 数据结构图,通过该指针,就可以找到 sal 模块用到的所有数据结构体。
Config 部分指向 linphone 的配置信息,包括网络, sip 协议, rtp 传输,音视频参数以及编解码器
信息。
call 指针挂载了所有的通话,每一路通话都由 linphonecall 结构体表示。
Audiostream 和 videostream 保存了媒体信息, a_rtp 和 a_rtcp 保存了 RTP 相关的信息。



4 linphone init

1) Ortp 库初始化,调用 ortp_init
在 ortp_init 中主要创建了 payload type 链表, 所有支持的 payload type 都被创建在一起了, 这里注
册的 payloadtype 就是底层 RTP 传输所能够支持的。
添加 payload type,先为音频,后为视频。
2) 调用 ms_init 初始化 mediastream 库,该库封装了媒体处理接口,使得多媒体数据的处理变得简
单。
Ms_init 中形成了三个全局链表,一个为 filter 描述符链表,所有 media streamer 支持的 filter 的描
述符在这里被串接在一起了,包括编解码,音视频读写以及 RTP 发送和接收处理相关的 filter。第二
个为音频卡描述符链表,所有支持的音频卡相关的描述符也被串接在一起。第三个就是视频卡描述
符链表,处理类似音频卡描述符。
3) 调用 sal_init 进行 sip 协议栈的初始化。该过程将返回一个 sal 结构体。
Exosip 全局结构体的创建以及初始化。
需要注意,在这里相当于有三层封装调用:一层为 sal 层的封装调用,一层为 exosip 层的封装调
用,最底层为 osip 层的基本调用。
另外需要注意的是在这里没有创建 exosip 任务, 而是在后面的读取并配置 sip 配置信息时才创建
exosip 任务,并监听特定端口。
将 lc->sal 的 up 指针指回 linphone core 全局结构体
设置 sal 上的回调函数,这些回调函数在对应的 sip 协议处理完后用于调用来处理外层有关 call
与 media 流的一些处理。
如果配置文件中没有设置 sip 会话的过期时间,则在这时将其设置为 200
将所有 sip setup 配置串联到 registered_sip_setups 全局链表上
4) 读取配置文件中有关音频的设置并对声卡进行设置
根据配置文件中描述的声卡设备 id 将声卡设备配置到 linphone core 结构体的 sound 配置结构体
对应声卡的描述项上。
设置用于响铃的音频文件的路径。该文件路径会被保存到 sound configure 结构体的 local_ring 项
目上。
类似上面,设置用于提示远端响铃的音频文件的路径,最终保存到 sound configure 结构体的
remote ring 项目上。
检查系统中的声卡设备
配置是否使能回声消除
配置回声限制
配置增益
配置回放增益
5) 读取配置文件中有关网络部分的配置并对网络参量进行配置
读取配置文件中有关带宽的设置, 并对 linphone 中音视频使用的带宽进行配置。 首先保存带宽值
到 net config 结构体中, 音频带宽配置为配置文件中读出的值与默认值的小者, 视频带宽配置为读出
值减去音频值减去 10 和 0 之间的较大者。这里 10 相当于是一个缓冲。
设置 stun server
设置 nat 防火墙地址
配置是否使用防火墙策略
配置是否只对 SDP 进行 nat 转换

配置 mtu 值
设置信息分包时间
6) 读取配置文件中有关 RTP 部分的配置并对该模块进行配置
首先设置音视频的 RTP 端口号
配置 RTP 音视频抖动补偿时间,默认为 60 毫秒
配置 nortp 超时时间,即没有 RTP 或者 rtcp 数据包时 linphone 认为对端 crash 或者网络中断的超
时值
设置在静音时不进行 RTP 传送,默认为 FALSE
7) 读取配置文件中有关编解码器相关的信息并设置到 linphone 的编码器信息结构体上
读取配置文件中的音频编解码器信息,如果有,则查找之前初始化 ms 模块时建立的全局过滤器
描述符信息表,如果找到,则说明相应的编解码器支持,否则不支持配置文件中添加的编解码器。
对于视频也类似。
通过上述操作,所有的配置文件中可支持的编解码器信息都被创建到 audio_codecs 和
video_codecs 两个链表上了,之后将它们添加到 linphone 的 codecs_conf 结构体上。
另外,在这步操作时,也将系统支持的其他编解码器添加到了 codes_conf 结构体上,基本原理
如下:对于 video,查找所有的 RTP 初始化时创建的 payloadtype 表,如果是 video 类型的 payload,
并且被 mediastreamer 支持,但是不在配置文件描述中,就将其添加到链表上。也就是系统初始化时
支持的但是在配置文件中没有说明的编解码器也会被添加到配置结构上。
更新已分配的音频带宽值。找到需要最大带宽的音频编解码器,将其带宽需求除以 1000 作为
linphone 的 audio_bw 值,同时重新设置 linphone 网络配置中的上下行带宽值。
Desc_list 上的编解码器信息与 RTP 初始化时设置的 payload type 信息的区别:
Desc_list 将 medie streamer 支持的所有 filter 的信息都串联在一个链表上, 不仅包括了编码器解码
器,还包括了 RTP 发送和接收 filter,以及音视频的读写 filter。这些都是站在 media streamer 这个中
间层的角度来考虑。 Filter 的构成也很规范,包括了一些必要的描述信息以及数据的读写处理接口。
Payload type 链表是所有 RTP 传输中支持的 payload 的链表,每一个 type 的描述信息主要描述了
该类型的 type 的时钟速率,正常比特率,采样比特等,可以看出这些信息与传输也是紧密相关的。
除此之外,也描述了编码信息,这部分信息与 desc_list 中的编码器信息部分会存在交集。
8) 读取配置文件中有关 sip 协议的相关信息,并以此来配置 linphone 的 sip 模块。
配置是否在发送数字时使用 sip info 信息。
配置是否在发送数字时使用 rfc2833 信息
配置是否使用 ipv6
配置 sip 的传输端口信息。指定是使用随机值还是知名 5060 端口
将端口信息设置到 linphone core 中,并启动 sip 监听。这样,当 sip 协议数据到达时即可被处理。
首先调用 sal_listen_port 启动监听端口。在这里,协议层被选择和设置,一般情况下都是 udp,
这里为 eXtl_udp。之后创建并启动_eXosip_thread 任务,该任务处理 sip 协议数据的接收,协议的处
理,状态机的处理,数据的发送等。即几乎所有与 sip 协议有关的处理都会在该任务中处理完。最
后保存用户代理信息。
获取配置文件中的联系人信息,如果联系人为空,或者配置文件中联系人信息不为空,但在将其
设置为主联系人信息时出错(比如格式错误),则基于环境变量中的 host 和 user 信息创建主联系人,
否则将配置文件中的联系人信息设置到 sip_conf 结构体的联系人上
如果配置文件中设置了猜测主机名,则将该配置设置到 linphone core 的 sipconfigure 结构体上
配置 incoming call 的超时时间,如果超过超时时间没有 answer 则 terminate 该 call
读取并配置代理信息,所有的代理者信息都被添加到 sip configure 的代理者链表上
读取并配置默认代理者信息。默认代理者会从所有代理者中挑选,根据配置文件,然后放到

linphone core 结构体的 default_proxy 上
读取并配置认证信息。首先从配置文件中读取 usrname, userid, password, ha1, realm 等信息,
并根据这些信息创建一个新的认证信息结构体, 将其添加到 linphone core 的 auth_info 链表上。同时,
查找所有处于 pending 状态的待认证事件,如果 linphone core 中能找到一致的认证信息结构体,则
对其进行认证。
根据配置文件对 sip_conf 的其他变量进行设置
9) 读取配置文件中有关 video 模块的配置并将其设置到 linphone core 中
首先读取所有的 video 设备,将其添加到 video_conf 的 cams 项上
读取配置文件中 video 部分有关 device 的设置, 在系统中查找是否已经有该设备。如果没有找到,
则使用 default 设备配置。如果原来保存的设备不为空,并且与新设备不一样,则基于新设备重新触
发 preview 预览。如果 linphone 已经准备好了,并且 video_conf 上的设备不为空,则将该设备的描
述串写入配置文件,也即覆盖之前的配置。另外,如果如果描述串中有 static picture 则描述串在被重
新写到配置文件之前会设置为空。
根据配置文件设置视频 size
根据配置文件设置其他配置项,包括是否进行 capture,是否 display,是否 self_view 等。
10) 设置 linphone 之前和当前的模式都为在线状态。设置最大 call logs 为 15
11) 读取并配置 ui
读取配置文件 friends section 部分的信息,创建 friends 结构体并保存配置。将所有的 friends 添
加到 friends 链表上。并额外做一些其他处理。
最后读取 call logs 信息,并创建 call logs 结构体保存 logs 信息,将所有 logs 添加到 call logs 链表
上。
12) 显示 ready。
全局状态配置为 power on
将 sip_conf 中的 auto_net_state_mon 设置到 linphone core 上
Linphone core 的 Ready 设置为 TRUE
至此,整个初始化过程完成。初始化后的内存数据结构体及状态:
首先需要创建 linphone 顶层最全局结构体 linphonecore 也即程序中多处用到的 lc。对于 phone 的
很多相关操作,该对象是主要的 handler,在整个程序运行过程中在内存中只有一个实例存在。
初始化 ortp 库,加载支持的音视频 RTP payload 类型到全局结构体 av_profile 上。同时全局结构
体 linphone_payload_types 也指向这些 payload 元素列表。
初始化 ms 库。 Desc_list 全局指针挂载了所有支持的 filter 描述符信息。 加载所支持的声卡设备的
描述符到全局变量 scm 上,加载支持的所有视频捕获设备的描述符到视频全局变量 scm 上。
初始化 sal 模块,在此过程中初始化 exosip 库,在 exosip 初始化过程中初始化了 osip 库。在此过
程中, 从下到上, osip 全局状态机被加载, osip 全局结构体对象也被创建, 其上的事务链表, callback
等子项也被设置, exosip 全局结构体对象也被创建,其上底层协议处理部分以及内存分配部分也部
分的被初始化, osip 对象被挂载到了 exosip 对象上。 Sal 结构体对象被分配内存。
Linphone core 被挂载到 sal 上了, sal 的回调处理函数被加载。
配置文件中的配置项被一步步的加载, 同时 linphone 的配置部分也被不断的在内存中创建出。 状
态被更新,整个初始化过程也就此完成。






你可能感兴趣的:(linphone分析)