1. 程式人生 > >linphone 分析1 linphone的架構和初始化

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.執行時候系統圖為


通話雙方在通訊前使用 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 的配置部分也被不斷的在記憶體中創建出。 狀
態被更新,整個初始化過程也就此完成。