標籤:des style blog http io ar os 使用 sp
http://blog.chinaunix.net/uid-15063109-id-4445165.html
————————————————————————————————————————————————————————————
pjsip功能很強,做sip rtp語音通話庫首選。在2.0之後,也可使用視訊。不過,它的視頻功能預設是從視頻裝置採集,然後進行編譯,再發送出去的。假設,我們已經有了視頻源,比 如IP攝像機,不需要採集和編碼這個過程,怎麼處理呢?假設我們採用pjsip附帶的pjsua為例。
通常的方法:
1 把視頻源當然檔案來處理,sample有。不過這種方法用的不多。
2 修改vid_stream.c,在put_frame和get_frame裡,換上我們自己的視頻源。這種方法使用的最多,很多人在1.x版本裡可使用視訊,就用這種方法。
3 本文採用的:重新構造sdp,自己建立rtp通道。
前兩個方法思路直接,代碼量都不小,尤其是第二種,需要修改pj底層代碼。
如果基於pjsua做一個簡單的視頻通訊,可以採用本文中的方法。其實代碼量也不小,不過sample提供了參考,實現起來也比較容易。下面簡單說明:
1 關鍵點在sdp上,pjsua_call_make_call這個函數非常方便,直接呼叫對方。不過它在底層做了太多工作,比如啟動了音效卡。而不用這個函數,直接用比較底層的pjsip_inv_send_msg,自己處理的工作相對比較多(但不難,不過這樣就不需要pjsua這個現成的程式了,所以我們繼續用pjsua_call_make_call)。
不過還好,pj庫提供了大量的回調,其中一個:on_call_sdp_created,就是在建立sdp後回調上來,由我們自己再修改。比如我們自己定義rtp的連接埠g_local_port。
void on_call_sdp_created(pjsua_call_id call_id,
pjmedia_sdp_session *sdp,
pj_pool_t *pool,
const pjmedia_sdp_session *rem_sdp)
{
int nPort;
if (sdp != NULL)
{
pjmedia_sdp_media *m = sdp->media[sdp->media_count-1];
m->desc.port = g_local_port;
pjmedia_sdp_conn *c = sdp->conn;
char* addr;
if (c)
addr= c->addr.ptr;
else
{
const pj_str_t *hostname;
pj_sockaddr_in tmp_addr;
char *addr;
hostname = pj_gethostname();
pj_sockaddr_in_init(&tmp_addr, hostname, 0);
addr = pj_inet_ntoa(tmp_addr.sin_addr);
sdp->conn = (pjmedia_sdp_conn *)pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
sdp->conn->net_type = pj_str("IN");
sdp->conn->addr_type = pj_str("IP4");
sdp->conn->addr = pj_str(addr);
}
sdp->origin.addr = *pj_gethostname();
}
}
同樣,這裡還可以修改payload type等。
這是發起呼叫時的,接收方收到後的回應之後,也會觸發這個回調,自己設定RTP連接埠,payload type就可以了。
2
呼叫成功後,雙方建立起串連關係,這時需要傳rtp資料了。pjsua把這些工作都放在底層了,不做任何修改,只需要在發送和接收時,自己做一些處理就行。
先說接收方(參考siprtp.c源碼):
pj_status_t init_local_rtp()
{
if (m_bInitMedia)
{
destroy_media();
}
//g_local_port = local_port;
pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
pool = pj_pool_create(&(cp.factory), "test", 1000, 512, NULL);
int status;
//status = pjmedia_endpt_create(&cp.factory, pjsip_endpt_get_ioqueue(pjsua_get_pjsip_endpt()), 0, &med_endpt);
status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
status = pjmedia_rtp_session_init(&video.out_sess, 97, pj_rand());
status = pjmedia_rtp_session_init(&video.in_sess, 97, 0);
status = pjmedia_transport_udp_create(med_endpt, NULL, g_local_port, 0, &video.transport);
m_bInitMedia = true;
video.active = true;
return 0;
}
這段代碼是本地啟動rtp一個連接埠用來接收視頻資料。
然後,從sdp得到對方發送的ip和連接埠,調用pjmedia_transport_attach,建立關聯就可以了。
發送方同樣調用上面函數,初始化本地連接埠,但不需要pjmedia_transport_attach。(假定視頻傳輸是單方向的)
發送動作就比較簡單了,先把要發的資料分包,大小不超過1400,然後pjmedia_rtp_encode_rtp,再pjmedia_transport_send_rtp。
上面還沒有講接收方是怎麼接資料的,這裡也用到了pj提供的回調機制:
status = pjmedia_transport_attach(video.transport, &video,
//&info.rem_addr,
&remote_addr,
NULL,
sizeof(pj_sockaddr_in),
&on_rx_rtp,
NULL);
這裡面的on_rx_rtp就是接收RTP的回調。
video.transport等定義:
struct media_stream
{
/* Static: */
unsigned call_index; /* Call owner.*/
unsigned media_index; /* Media index in call.*/
pjmedia_transport *transport; /* To send/recv RTP/RTCP*/
/* Active? */
pj_bool_t active; /* Non-zero if is in call.*/
/* Current stream info: */
pjmedia_stream_info si; /* Current stream info.*/
/* More info: */
unsigned clock_rate; /* clock rate*/
unsigned samples_per_frame; /* samples per frame*/
unsigned bytes_per_frame; /* frame size.*/
/* RTP session: */
pjmedia_rtp_session out_sess; /* outgoing RTP session*/
pjmedia_rtp_session in_sess; /* incoming RTP session*/
/* RTCP stats: */
pjmedia_rtcp_session rtcp; /* incoming RTCP session.*/
/* Thread: */
pj_bool_t thread_quit_flag; /* Stop media thread.*/
pj_thread_t*thread; /* Media thread.*/
};
struct media_stream video;
使用pjsip傳輸已經編碼的視頻