主頁(http://www.130131.com):使用pjsip傳輸已經(jīng)編碼的視頻 jsip功能很強,做sip rtp語音通話庫首選。在2.0之后,也支持視頻。不過,它的視頻功能缺省是從視頻設(shè)備采集,然后進(jìn)行編譯,再發(fā)送出去的。假設(shè),我們已經(jīng)有了視頻源,比如IP攝像機,不需要采集和編碼這個過程,怎么處理呢?假設(shè)我們采用pjsip附帶的pjsua為例。 通常的方法: 1 把視頻源當(dāng)然文件來處理,sample有。不過這種方法用的不多。 2 修改vid_stream.c,在put_frame和get_frame里,換上我們自己的視頻源。這種方法使用的最多,很多人在1.x版本里支持視頻,就用這種方法。 3 本文采用的:重新構(gòu)造sdp,自己創(chuàng)建rtp通道。 前兩個方法思路直接,代碼量都不小,尤其是第二種,需要修改pj底層代碼。 如果基于pjsua做一個簡單的視頻通信,可以采用本文中的方法。其實代碼量也不小,不過sample提供了參考,實現(xiàn)起來也比較容易。下面簡單說明: 1 關(guān)鍵點在sdp上,pjsua_call_make_call這個函數(shù)非常方便,直接呼叫對方。不過它在底層做了太多工作,比如啟動了聲卡。而不用這個函數(shù),直接用比較底層的pjsip_inv_send_msg,自己處理的工作相對比較多(但不難,不過這樣就不需要pjsua這個現(xiàn)成的程序了,所以我們繼續(xù)用pjsua_call_make_call)。 不過還好,pj庫提供了大量的回調(diào),其中一個:on_call_sdp_created,就是在創(chuàng)建sdp后回調(diào)上來,由我們自己再修改。比如我們自己定義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等。 這是發(fā)起呼叫時的,接收方收到后的回應(yīng)之后,也會觸發(fā)這個回調(diào),自己設(shè)定RTP端口,payload type就可以了。 2 呼叫成功后,雙方建立起連接關(guān)系,這時需要傳rtp數(shù)據(jù)了。pjsua把這些工作都放在底層了,不做任何修改,只需要在發(fā)送和接收時,自己做一些處理就行。 先說接收方(參考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一個端口用來接收視頻數(shù)據(jù)。 然后,從sdp得到對方發(fā)送的ip和端口,調(diào)用pjmedia_transport_attach,建立關(guān)聯(lián)就可以了。 發(fā)送方同樣調(diào)用上面函數(shù),初始化本地端口,但不需要pjmedia_transport_attach。(假定視頻傳輸是單方向的) 發(fā)送動作就比較簡單了,先把要發(fā)的數(shù)據(jù)分包,大小不超過1400,然后pjmedia_rtp_encode_rtp,再pjmedia_transport_send_rtp。 上面還沒有講接收方是怎么接數(shù)據(jù)的,這里也用到了pj提供的回調(diào)機制: 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的回調(diào)。 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; (中國集群通信網(wǎng) | 責(zé)任編輯:李俊勇) |




