沙愛軍,沈衛康,毛其林
(南京工程學院通信工程學院,江蘇南京,211167)
SIP(Session Initiation Protocol)會話初始協議,最新的SIP 規范為RFC 3261。作為應用層控制信令協議,SIP具有簡單、靈活的特點,正逐步取代H.323 協議成為 VoIP 的標準協議。
SIP 基于客戶機/服務器結構。用戶代理(User Agent ),即SIP 終端,是SIP 系統中的最終用戶,可分為:用戶代理客戶端UAC,用于發起呼叫請求;用戶代理服務器UAS,用于響應呼叫請求,一個終端往往既可以充當UAC,也可以充當UAS。
在一個VoIP網絡中,UA作為終端設備,需求大,而且對移動便攜和價格也有要求。嵌入式設備具有便攜、專用、可裁剪、性價比等優點,本文將在嵌入式平臺上實現一個基于SIP的的語音電話終端,并對系統實現中所需的關鍵技術和工作過程進行介紹。
需求分析可知,作為一個語音電話終端,需要如下幾個方面的功能:(1)要有音頻采集/播放設備以及相關的編解碼算法;(2)要有網卡處理設備,傳輸雙方的信令和音頻數據;(3)要采用實時傳輸協議保證語音傳輸質量。基于上述分析,系統的實現框架可設計如圖1,后面將具體闡述。

圖1 UA 實現框架
硬件平臺采用一款ARM開發板:CPU為三星公司生產的主頻可達400MHz的S3C2440A;音頻處理設備采用Philips公司生產的UDA1341TS,可實現A/D的相互轉換, S3C2440A 通過 L3接口可以控制和配置 UDA1341TS,L3 接口有 L3MODE、L3DATA和 L3CLOCK 三根信號線,音頻的驅動采用OSS架構,類似其他驅動,音頻驅動的實現分為初始化、打開設備、讀寫操作、設備查詢/控制、釋放設備和注銷設備等部分;開發板帶有網絡芯片DM9000,滿足聯網的要求;系統還帶有串口等其他設備。本次使用的內核Linux-2.6.32.2 已經能支持UDA1341 音頻芯片的驅動,但提供的是針對SMDK2440,SMDK2440的錄音通道,使用的是VIN1,而在本開發板使用的則是VIN2,因此需修改內核驅動linux-2.6.32.2/sound/soc/codecs/ uda134x.c中添加uda134x_write(codec, 2, 2|(5U<<2)),把錄音通道改為VIN2。
軟件平臺采用linux-2.6.32.2,Linux作為一種開源操作系統,具有較高的穩定和可移植性,可以根據需要進行內核、驅動的裁剪配置,這里將TCP、UDP協議以及聲卡,網卡、串口等相關的驅動配置進去。
SIP協議作為會話控制協議,用來建立、修改和終止多媒體會話,其重要性不言而喻。這里對SIP再做點介紹。
SIP采用分層結構,最低層是語法和編碼,第2層是傳輸層,第3層是事務層。最上層是事務用戶。每個有狀態的SIP 實體,都是事務用戶,當發送請求時,將創建一個客戶端事務實例,并將請求與目的IP 地址、端口一起發送。SIP消息分為兩大類:請求(Request)和 響應(Response。請求消息分為 6 種:REGISTER、INVITE、ACK、BYE、CANCEL、OPTIONS。響應消息分為6類:1xx,2xx,3xx,4xx,5xx,6xx。其中1xx是臨時響應(Provisional Response),其余是最終響應(Final Response)。
目前符合RFC3261規范的開源SIP協議棧有OPAL,VOCAL,sipX ,oSIP[5]等,oSIP 結構簡單、功能齊全,可移植性好,因此本次選用了oSIP。
oSIP協議棧主要分為3大部分:狀態機模塊、解析器模塊、工具和對話模塊。其核心是狀態機模塊。狀態機有4種類型:ICT、IST、NICT以及 NST,對不同的事務,oSIP用不同的狀態機處理,并推動自身狀態的改變。解析器模塊主要完成對SIP 消息結構剖析、SDP 消息的結構剖析以及URI 結構的剖析。工具模塊主要提供一些處理工具用于對話管理和SDP協商。
eXosip是對oSIP的擴展,它對oSIP協議棧進行了部分封裝,增加了registration、 call、dialog等過程的解析,使得調用接口更加友好,使用eXosip可以簡化開發任務。
流媒體處理分為3個部分Mediastreamer2和ORTP以及編解碼算法。這3個本來是獨立的,由于它們相互協作處理用于處理音頻流,因此這里一起介紹。
Mediastreamer2負責媒體流的處理,采用純C開發,是一個功能強大且小巧的流引擎,最小依賴只需oRTP和LIBC,可根據需要添加其他庫,如SPEEX。Mediastreamer2媒體庫將語音模塊的工作分為幾大MSFilter實體:對聲卡的讀寫soundread/soundwrite,調用音頻庫的encode/decode,接收/發送RTP數據包的rtpsend/rtprecv等,利用函數指針將一系列的FILTER組合起來,多個MSFilter進行連接,形成一個MSFilter chain。
oRTP負責流媒體如何安全可靠的傳輸,是用C語言編寫的RTP的開源協議棧,并且實現了RTCP,RTP在初始化的時候會對全局變量RtpProfile賦值,RtpProfile中的PayloadType數組的每個元素對應一種媒體流,包括了名稱,采樣率,聲音位數,靜音格式,占用帶寬等媒體流屬性。rtp_session_send_with_ts和rtp_session_recv_with_ts分別是oRTP發送和接收的主要函數。
語音壓縮編解碼算法很多,常見的有G.711PCM、GSM等算法,本次采用的G.711PCM算法,如需要更多算法,還可以使用SPEEX庫。
本次UA部分實現了注冊、呼叫、應答、掛斷、退出 等電話呼叫的基本功能。UA主程序的流程圖如圖2示。

圖2 UA主流程圖

圖3 mysip_init()執行流程圖
(1)系統首先調用add_port_set(),設置注冊時所用的注冊服務器地址和端口號,通話時對端的sip url地址及本地sip端口和rtp端口號、用戶名和密碼等;然后調用mysip_init()。
(2)在mysip_init()中,進行一系列初始化,見圖3。首先調用eXosip_init()對eXosip變量、osip庫初始化,設置回調函數以及傳輸方式;mysip_init()接著調用eXosip_listen_addr(),eXosip_listen_addr()則對開始監聽sip端口,并執行eXosip_execute ()去讀取各種osip事件,并進行處理:對于要發送的message,管理程序會主動調用接口發送,對于接收到的message,可能會創建新的call、新的transaction,生成新的transaction的event,還有exosip的event,其中exosip的event是上報給管理程序的;在mysip_init()中會創建一個新的線程ua_exosip來處理這些eXosip事件,為便于描述,在(3)中再討論這些事情,該線程創建后,會對UAC和UAS事件分別作出處理,見圖4;mysip_init()繼續完成初始化的調用ortp()和ms_init(),分別對oRTP協議棧和對Mediastreamer2初始化。
(3)ua_exosip()線程創建后就一直獨立運行,在程序初始化完成后,它的運行要和主程序的循環讀取用戶輸入的命令并做出響應的運行結合起來。
在ua_exosip中的exosip事件處理有很多,這里僅僅選取了UAS端收到的EXOSIP_CALL_INVITE, EXOSIP_CALL_ACK以及UAC端收到的EXOSIP_CALL_RINGING,EXOSIP_CALL_ANSWERED在圖4中展示。
(4)在主程序中進入一個死循環中,循環中不斷讀取當前輸入的命令,并根據命令的輸入,執行相應的處理函數。圖2中,列舉了一些常見的命令,當輸入r時,代表要向注冊服務器注冊,將執行函數sregister()完成此功能;當輸入i時,代表作為UAC,將執行函數sinvite()發起一次呼叫;當輸入a時,代表作為UAS,將執行函數sanswer()接聽電話;當按下h時,代表掛斷電話,將執行shang();當按下q時,代表退出系統,將執行squit()。這里分別以UAC端的sinvite()和UAS端的sanswer()為例,介紹實現過程。
圖5為UAC端的sinvite()過程,由于這是一個呼叫發起請求,將主要調用eXosip_call_build_initial_invite(&invite, dest_call, source_call, NULL, "invite")和eXosip_call_send_initial_invite (invite)等,它們將構造一個invite 請求信息,創建一個新的call,并為該invite創建一個新的transaction,創建完call和transaction后,會根據要發送的invite生成一個transaction的event,并將event添加到transaction的event隊列中,最后喚醒處理線程對transaction上的event處理。
圖6 為UAS端的sanswer()過程。當接收方接聽一個電話時,將向發起方發送200OK的信息,此時將建立起來一次dialog。但正式的通話要等到發起方發出ack信息之后。
(5)終端的運行分析
結合圖4、圖5、圖6來分析一次執行的過程。當在主程序中輸入i命令時,UAC將調用sinvite()發起一次呼叫,向接收端發送呼叫請求(INVITE),接收端UAS收到了來自EXOSIP層的EXOSIP_CALL_INVITE,則將執行圖4中的EXOSIP_CALL_INVITE分支下的處理程序,顯示“收到來自xx的呼叫”,本地振鈴,發送“180”消息給UAC;當UAC收到來自EXOSIP層的EXOSIP_CALL_RINGING時,會顯示“對方已振鈴”;UAS端打算接聽電話,在主程序中輸入a命令,則調用sanswer()接聽,這里將進行媒體協商,并發送200OK消息給UAC,表明已應答,一次dialog建立;UAC收到來自EXOSIP報告的EXOSIP_CALL_ANSWERED,將“顯示對方已通話”,并構建、發送ACK消息給UAS,獲取編碼方式,調用start_media()函數進行媒體流的建立,為隨時準備利用RTP傳輸通話語音;UAS收到來自EXOSIP報告的EXOSIP_CALL_ACK,將會顯示“通話開始”,同時調用start_media()函數進行媒體流的建立。start_media()中將調用Mediastreamer2和oRTP來進行音頻數據的RTP傳輸。
在移植好開發板所用的內核和文件系統后,再交叉編譯libosip、libexosip、mediastream、ortp等庫和應用程序myua.c等,得到可執行文件myua,將可執行文件和前述生成的動態庫拷貝到開發板的相應目錄下,就可以運行。

圖4 ua_exosip中的exosip事件處理

圖5 UAC端的sinvite()過程

圖6 UAS端的sanswer()過程
系統的運行環境:在一個局域網內,由一臺在ubuntu下運行的brekeke sip sever擔任服務器,提供注冊員、代理服務、定位等功能;客戶端由兩個移植了UA的開發板組成,地址和用戶名見圖7。運行時,假設1002(此時擔當UAC)向1001(此時擔當UAS)發起呼叫:先將兩者分別運行注冊;再在1002上輸入i,則會發起一次呼叫;1001收到后輸入a應答;1002收到發ACK則兩者建立語音通話。

圖7 系統的運行環境
測試表明,局域網內RTP包的丟包率為0,通話通話質量較為清晰,通話情況良好。進一步的工作可以實現視頻通話,增加SPEEX等語音編碼,使其能用于更多的場合。
[1]Rosenberg J.SIP: Session Initiation Protocol[S].RFC3261 ,IETF , 2002
[2]張遼.基于ARM 的嵌入式 IP 電話系統設計與實現[D].武漢:華中科技大學,2012.1.
[3]劉剛,趙劍川.Linux系統移植 [M].北京: 清華大學出版社, 2011.1.
[4]友善之臂.Mini2440 之Linux 移植開發實戰指南[EB].http://www.arm9.net/.2010.04
[5]潘健等.基于Mediastreamer2的嵌入式語音終端的實現[J],湖北工業大學學報,2012,27(5):48-51
[6]胡斌.VoIP系統中基于RTP/RTCP協議的語音及視頻傳輸的設計與實現[D].廈門:廈門大學,2009.05