SIP與RTP綜合應(yīng)用(轉(zhuǎn))

SIP是一個(gè)會(huì)話協(xié)議锋恬,很多大企業(yè)都在用,通信行業(yè)的一個(gè)標(biāo)準(zhǔn),其業(yè)務(wù)邏輯比較,簡(jiǎn)單地來(lái)說(shuō)如下:

User Agent?Server

?------------------REGISTER----------->

?<----------401(407) Unauthorized--

?----------REG(帶上用戶口令)----------->

?---------------200 OK?1 Bindings---

雙方交互幾次,注冊(cè)成功豹爹。

因?yàn)镾ip 通信一般采用UDP裆悄,所以有個(gè)泵疲活的問(wèn)題,一般每隔兩三分鐘再向server注冊(cè)一下光稼。server也可能每隔一兩分鐘向客戶發(fā)Unauthorized或南,讓客戶再刷新一下登錄孩等。

登錄成功后,某個(gè)客戶端向另一個(gè)客戶端發(fā)起呼叫采够,通過(guò)服務(wù)器中轉(zhuǎn)命令肄方。簡(jiǎn)單來(lái)講,這個(gè)和IM的原理是一樣的蹬癌。對(duì)方同意接收呼叫后权她,把媒體端口通知給server 及對(duì)方。到了這里逝薪,有IM開(kāi)發(fā)經(jīng)驗(yàn)的人隅要,自然就知道下一步怎么做了:如果想P2P直連的話,就先穿透NAT打洞董济,否則就通過(guò)Server中轉(zhuǎn)步清。

很明顯,SIP會(huì)話和現(xiàn)有的IM類似虏肾,但效率或效果上來(lái)講差的很多廓啊,比如登錄保活封豪, 還是同名用戶同時(shí)登錄等等谴轮,都處理的不夠好。不過(guò)SIP是電信協(xié)議吹埠,最初是用在VOIP和可視電話上书聚,環(huán)境比IM簡(jiǎn)單地多,所以這個(gè)協(xié)議足夠用了藻雌,估計(jì)名字中的S也是因?yàn)檫@個(gè)原因雌续。

sip呼叫成功,建立連接之后胯杭,媒體傳輸(音視頻)是通過(guò)RTP協(xié)議進(jìn)行的驯杜。簡(jiǎn)單地說(shuō),采集到聲音和視頻做个,先按指定編碼方面編碼鸽心,比如音頻編碼成 g711,視頻編碼成h263,然后根據(jù)RFC相關(guān)協(xié)議加上包頭用UDP向指定發(fā)送出去居暖。對(duì)方收到后先解包顽频,再解碼,然后播放太闺。

如果想了解SIP的詳細(xì)工作流程糯景,可以這樣:

1 找一個(gè)外網(wǎng)的sip server (如果有經(jīng)驗(yàn),可以用yate2,或Trixbox等自己搭建)

2 安裝x-lite ( 很不錯(cuò)的sip軟電話客戶端,如果安裝eyeBeam更好蟀淮,帶視頻)

3 安裝ethereal和WinPcap (抓包工具)

然后最住,用x-lite撥打其他的客戶端或SIP話機(jī),用抓包工具抓出相關(guān)的數(shù)據(jù)包怠惶,先看流程涨缚,然后再看包結(jié)構(gòu)。

后面附上一個(gè)介紹SIP的PPT策治,寫(xiě)的非常好脓魏,可能是臺(tái)灣方面出品,以前收集的通惫。是個(gè).rar文件轧拄,因?yàn)檫@里只能上傳圖片,所以改名為.jpg再上傳讽膏,下載后把.jpg去掉解壓就可以了檩电。

PPT寫(xiě)的非常好,用心看府树,很快就能了解SIP的工作流程俐末。

下一步,就是自己動(dòng)手實(shí)現(xiàn)SIP VOIP系統(tǒng)了奄侠。

如果商用的話卓箫,server 采用Trixbox,也可以仔細(xì)研究一下 Asterisk÷⒊保客戶端就用x-lite好了烹卒。

做為程序員,第一反應(yīng)就是怎么樣自己動(dòng)手寫(xiě)一個(gè)客戶端弯洗,甚至服務(wù)器旅急。好在開(kāi)源產(chǎn)品眾多,寫(xiě)一個(gè)并不難牡整。

經(jīng)過(guò)幾天的調(diào)試藐吮,發(fā)現(xiàn)幾個(gè)協(xié)議棧做的不錯(cuò):

1 SIP協(xié)議棧:

?a osip+exosip (建立客戶端及通信非常簡(jiǎn)單,質(zhì)量也好)逃贝,

?b reSIProcate (全面谣辞,有server端例子,綜合調(diào)試方便)沐扳。

?c 其他的還用過(guò)一個(gè)pjsip泥从,不過(guò)它與音視頻結(jié)合成一個(gè)庫(kù)之后, 音頻質(zhì)量不好沪摄。但是比較小巧躯嫉, 聽(tīng)說(shuō)臺(tái)灣很 多嵌入設(shè)備采用纱烘。

2 RTP協(xié)議棧:

?a?Linphone采用的是oRTP,音視頻部分采用的是 MediaStreamer2

?b?JRtpLib,結(jié)合emiplib的音視頻處理和敬。

?c?ffmpeg,ffmpeg本來(lái)是專門(mén)處理音視頻編解碼的凹炸,不過(guò)也提供了rtp,rtsp,最近好象也增加了rtmp協(xié)議的支持戏阅。順便一提昼弟,MS2和emiplib底層也采用了ffmpeg。只要和音視頻打交道奕筐,并且質(zhì)量很不錯(cuò)的產(chǎn)品舱痘,都離不開(kāi)它,比如mplayer,ffdshow离赫。順便BS一下kmplayer芭逝,上了ffmpeg黑名單。

?這里面著重提到的是jrtplib渊胸,之前誤解為它只是按RTP傳輸數(shù)據(jù)包旬盯,以前寫(xiě)過(guò)的幾個(gè)文章,都是在RTP包之后翎猛,自己再封裝了一下胖翰,當(dāng)然,做為自己用的音視頻聊天程序切厘,這樣是沒(méi)問(wèn)題的萨咳。但用在SIP及其他VOIP產(chǎn)品上,要考慮互通疫稿,就要嚴(yán)格摟RTP協(xié)議來(lái)執(zhí)行了培他。

了解了幾個(gè)開(kāi)源的東西,下面自己動(dòng)手建一個(gè)簡(jiǎn)單的SIP環(huán)境:

1 對(duì)Linux比較熟的人遗座, 在CentOS上安裝Asterisk礁击,客戶端采用Linphone,自己研究吧覆糟。

2 象我這樣只要在Linux下用點(diǎn)g++的犀盟,如果想針對(duì)VOIP快速學(xué)習(xí)的話,服務(wù)器安裝yate2碎绎,客戶端隨便拿哪個(gè)都行螃壤。

3 如果自己想定制sip server,干脆一步到位筋帖,下載reSIProcate奸晴,用vc2005編譯,一次通過(guò)日麸。運(yùn)行時(shí)提示缺少幾個(gè)dll,google一下很快都找到了寄啼,然后運(yùn)行repro逮光,做為server先臨時(shí)用著,反正是學(xué)習(xí)墩划。

?客戶端呢涕刚,網(wǎng)上流行一個(gè)很不錯(cuò)的,名字叫Youtoo,下載乙帮,簡(jiǎn)單編譯后可以做為一個(gè)語(yǔ)音的客戶端使用杜漠。

然后,PC上安裝幾個(gè)虛擬機(jī)察净,一個(gè)運(yùn)行server驾茴,一個(gè)運(yùn)行x-lite(做為一個(gè)參考的標(biāo)準(zhǔn)),主要上運(yùn)行我們自己寫(xiě)的客戶端進(jìn)行測(cè)試氢卡。如果要調(diào)試server锈至,就是主機(jī)上運(yùn)行repro,虛擬上分別運(yùn)行兩個(gè)x-lite。

環(huán)境搭建立好了译秦,下一步就開(kāi)始調(diào)試峡捡。

根據(jù)這幾天的實(shí)踐,找出了一個(gè)最優(yōu)的配置:

1 sip server采用Trixbox筑悴,如果對(duì)Linux很熟建議直接用Asterisk.

2 客戶端如果直接使用们拙,建議ekiga.

順便說(shuō)一下幾個(gè)客戶端使用的感受:

1 linphone:好象名氣不小,不過(guò)雷猪,最新版3.1.2安裝后啟動(dòng)就崩潰睛竣。我安裝的是普通的XP-SP3,電腦公司特別版求摇。一般軟件運(yùn)行沒(méi)問(wèn)題射沟。如果在這個(gè)平臺(tái)上都崩潰,真不知道說(shuō)什么好与境。

?后來(lái)再試3.1.1验夯,這個(gè)可以啟動(dòng),運(yùn)行能看到視頻圖像摔刁。不過(guò)奇怪的是挥转,與視頻電話連接上視頻窗口反而隱藏起來(lái)。結(jié)束通話共屈,又顯示出來(lái)绑谣。搞不懂它的視頻功能是做什么用的。另外顯示本地視頻只支持QCIF拗引。

2 eyeBeam :名氣更大借宵,使用起來(lái)也不錯(cuò),這個(gè)不錯(cuò)不包含視頻功能矾削。如果啟動(dòng)了視頻的話壤玫,只顯示第一幀圖豁护,攝像頭怎么轉(zhuǎn)它也不動(dòng)。另外欲间,主動(dòng)連視頻電話時(shí)楚里,不能啟動(dòng)視頻功能。視頻電話呼叫它才能啟動(dòng)猎贴。啟動(dòng)后班缎,"Start Video"點(diǎn)一下又灰住,完全不能用嘱能。

3 Wengo不錯(cuò)吝梅,現(xiàn)在改名叫Quate了虱疏,連接攝像頭非橙锹睿快,本地預(yù)覽也正常做瞪。不過(guò)对粪,解碼有問(wèn)題,看 到的是一堆綠色圖塊装蓬,不知道是它解不了碼著拭,還是弄幾個(gè)顏色塊在那邊騙人玩。

4 yate好象不支持視頻牍帚,不過(guò)聲音倒是不錯(cuò)儡遮。

5 回頭再說(shuō)ekiga,邊續(xù)試用了上面幾個(gè)軟件暗赶,以為我用的視頻電話硬件有問(wèn)題鄙币,但用ekiga連接后,雙方的視頻都正常蹂随。

上面是從視頻效果角度出發(fā)來(lái)評(píng)測(cè)的十嘿,如果不使用視頻的話都差不多。

搭建好環(huán)境岳锁,測(cè)試通過(guò)绩衷,熟悉協(xié)議之后, 就是自己做一個(gè)這樣的平臺(tái)了激率。

服務(wù)器想都不用想咳燕,直接用Tixbox,重頭寫(xiě)不現(xiàn)實(shí)乒躺。

至于客戶端招盲,一般的程序架構(gòu)應(yīng)該如下:

一 協(xié)議部分:

主要處理sip的注冊(cè),呼叫聪蘸,接收宪肖,掛機(jī)等功能表制,所有的協(xié)議都差不多,隨便選一個(gè)就行控乾。

二 媒體傳輸么介,這部分比較復(fù)雜 :

1 音視頻采集

2 音視頻編碼

3 音視頻編碼后組RTP

4 RTP/RTCP發(fā)送

5 RTP/RTCP接收

6 從RTP解包還原成編碼后的音視頻

7 音視頻解碼

8 音視頻播放

一般如果分配任務(wù),快速做一個(gè)客戶端蜕衡,首先想到的就是找一個(gè)開(kāi)源壤短,編譯出來(lái)再修改。

不過(guò)慨仿,試了幾個(gè)久脯,極度痛苦,分別說(shuō)一下镰吆。

1 Linphone:

這個(gè)產(chǎn)品只能算一般帘撰,不過(guò)用到的lib非常不錯(cuò),exosip+osip為sip命令服務(wù)万皿,ortp+mediastreamer2為流媒體服務(wù)摧找。不過(guò),編譯真是麻煩牢硅,別的不說(shuō)蹬耘,光mediastreamer2就用到了ffmpeg,gsm,ortp,srtp,openssl,speex, theora等,稀里湖涂足足花了大半天時(shí)間把所有這些都編譯好减余,然后編譯ms2.lib時(shí)提示幾個(gè)鏈接出錯(cuò)综苔。因?yàn)槲铱吹骄W(wǎng)上幾個(gè)文章說(shuō)明是用vc2005輕松編譯出來(lái)的,我也用的vc2005位岔。估計(jì)用mingw會(huì)簡(jiǎn)單一些如筛。不過(guò),已經(jīng)耗了近一天的時(shí)間赃承,感覺(jué)不爽妙黍,放棄。估計(jì)是linphone估計(jì)搞的復(fù)雜瞧剖,好讓antisip賣錢(qián)拭嫁。

2 ekiga

這個(gè)需要ptlib,第一感覺(jué)這東西很麻煩,不過(guò)編譯時(shí)出奇的順利(關(guān)鍵是官方提供的資料詳細(xì)抓于,網(wǎng)友寫(xiě)的文章也詳細(xì))做粤。然后編譯opal,也很順利捉撮。(只是占用機(jī)器比較厲害怕品,P4 2.6的占CPU極嚴(yán)重。不過(guò)巾遭,用的機(jī)器是聯(lián)想的超薄機(jī)箱那種肉康,不排除官方弄個(gè)很爛的CPU冒充闯估。因?yàn)閾Q到另一個(gè)P4 3G,速度快上兩三倍)吼和。

其實(shí)涨薪,編譯好opal,基本就可以了炫乓,它帶了很不錯(cuò)的例子刚夺,撥打電話接聽(tīng)都不錯(cuò)。

最后編譯ekiga時(shí)末捣,需要交叉編譯侠姑,直接放棄掉,有那時(shí)間不如好好研究opal了箩做。

3 其他

編譯了一下emiplib,這個(gè)庫(kù)寫(xiě)的真不錯(cuò)莽红。雖然封裝的比較深,不過(guò)調(diào)用時(shí)卒茬,可以選擇比較靠上的類來(lái)調(diào)用船老,有點(diǎn)類似ACE咖熟。只是視頻格式少了一點(diǎn)圃酵。

回頭再看上面的一般結(jié)構(gòu),SIP部分不用操心馍管,隨便找個(gè)庫(kù)就能達(dá)到目的郭赐,關(guān)鍵是媒體傳輸這部分。仔細(xì)看1-8這些部分确沸,很多我們自己動(dòng)手就可以做捌锭,其實(shí)我們并不需要一下完整的全功能的庫(kù)。

比如罗捎,音頻采集播放用DirectSound,視頻采用播放用DirectShow.編解碼用ffmpeg編譯出來(lái)的libavcodec,傳輸用jrtplib.這么一看观谦,只有3 音視頻編碼后組RTP和6 從RTP解包還原成編碼后的音視頻 這兩部分相對(duì)陌生,其他的都能找到成熟的代碼桨菜』碜矗基于這個(gè)想法,就不用上述開(kāi)源產(chǎn)品倒得,直接自己寫(xiě)一個(gè)好了泻红。

先做準(zhǔn)備工作:

1 編譯好ffmpeg及所帶的libavcodec等幾個(gè)lib和dll,音視頻編解碼時(shí)需要。

2 利用DirectShow做視頻采集霞掺。播放就直接用GDI畫(huà)圖好了谊路,簡(jiǎn)潔。

3 聲音部分菩彬,參考Youtoo這個(gè)程序缠劝,連SIP都有了潮梯,用現(xiàn)成的exosip,osip,ms2.lib(這個(gè)庫(kù)不支持視頻,否則就不用做上面那些苦力了)惨恭。音質(zhì)相當(dāng)不錯(cuò)酷麦。

4 傳輸就用jrtplib,不過(guò)開(kāi)始為了調(diào)試方便喉恋,自己寫(xiě)的UDP socket沃饶。

這些準(zhǔn)備工作做好,下一步就開(kāi)始參考RFC進(jìn)行RTP的組包和解包了轻黑。

RTP接收部分比較簡(jiǎn)單(不用考慮jitterbuffer等)糊肤,先從這里入手。

其實(shí)主要就3步:

1 創(chuàng)建一個(gè)udp氓鄙,監(jiān)聽(tīng)一個(gè)端口馆揉,比如5200。

2 收到RTP包抖拦,送到解包程序升酣,繼續(xù)收第 二個(gè)。

3 收齊一幀后态罪,或保存文件噩茄,或解碼去播放。

下面詳細(xì)說(shuō)一下具體過(guò)程:

1 創(chuàng)建UDP复颈,非常非常地簡(jiǎn)單(這里只是簡(jiǎn)單地模擬RTP接收绩聘,雖然能正常工作,但是沒(méi)有處理RTCP部分耗啦,會(huì)影響發(fā)送端):

lass CUDPSocket : public CAsyncSocket

{

public:

?CUDPSocket();

?virtual ~CUDPSocket();

?virtual void OnReceive(int nErrorCode);

};

調(diào)用者:CUDPSocket m_udp; m_udp.Create(...);這樣就可以了凿菩。注意端口,如果指定端口創(chuàng)建不成功帜讲,就端口+1或+2重試一下衅谷。

重寫(xiě)OnReceive:

void CUDPSocket::OnReceive(int nErrorCode)

{

?char szBuffer[1500];

?SOCKADDR_IN sockAddr;

?memset(&sockAddr, 0, sizeof(sockAddr));

?int nSockAddrLen = sizeof(sockAddr);

?int nResult = ReceiveFrom(szBuffer, 1500, (SOCKADDR*)&sockAddr, &nSockAddrLen, 0);

?if(nResult == SOCKET_ERROR)

?{

?return;

?}

//如果必要可以處理對(duì)方IP端口

?USHORT unPort = ntohs(sockAddr.sin_port);

?ULONG ulIP = sockAddr.sin_addr.s_addr;

//收到的數(shù)據(jù)送去解碼

?Decode((BYTE*)szBuffer, nResult);

}

2 收到了數(shù)據(jù),開(kāi)始Decode似将,一般通過(guò)RTP傳輸?shù)囊曨l主要有h263 (old,1998,2000),h264,mpeg4-es获黔。mpeg4-es格式最簡(jiǎn)單,就從它入手玩郊。

如果了解RFC3160肢执,直接分析格式寫(xiě)就是了。如果想偷懶译红,用現(xiàn)成的预茄, 也找的到:在opal項(xiàng)目下,有個(gè)plugins目錄,視頻中包含了h261,h263,h264,mpeg4等多種解包耻陕,解碼的源碼拙徽,稍加改動(dòng)就可以拿來(lái)用。

首先看:video\common下的rtpframe.h這個(gè)文件诗宣,這是對(duì)RTP包頭的數(shù)據(jù)和操作的封裝:

#ifndef __RTPFRAME_H__

#define __RTPFRAME_H__ 1

#ifdef _MSC_VER

#pragma warning(disable:4800)?// disable performance warning

#endif

class RTPFrame {

public:

?RTPFrame(const unsigned char * frame, int frameLen) {

?_frame = (unsigned char*) frame;

?_frameLen = frameLen;

?};

?RTPFrame(unsigned char * frame, int frameLen, unsigned char payloadType) {

?_frame = frame;

?_frameLen = frameLen;

?if (_frameLen > 0)

?_frame [0] = 0x80;

?SetPayloadType(payloadType);

?}

?unsigned GetPayloadSize() const {

?return (_frameLen - GetHeaderSize());

?}

?void SetPayloadSize(int size) {

?_frameLen = size + GetHeaderSize();

?}

?int GetFrameLen () const {

?return (_frameLen);

?}

?unsigned char * GetPayloadPtr() const {

?return (_frame + GetHeaderSize());

?}

?int GetHeaderSize() const {

?int size;

?size = 12;

?if (_frameLen < 12)

?return 0;

?size += (_frame[0] & 0x0f) * 4;

?if (!(_frame[0] & 0x10))

?return size;

?if ((size + 4) < _frameLen)

?return (size + 4 + (_frame[size + 2] << 8) + _frame[size + 3]);

?return 0;

?}

?bool GetMarker() const {

?if (_frameLen < 2)

?return false;

?return (_frame[1] & 0x80);

?}

?unsigned GetSequenceNumber() const {

?if (_frameLen < 4)

?return 0;

?return (_frame[2] << 8) + _frame[3];

?}

?void SetMarker(bool set) {

?if (_frameLen < 2)

?return;

?_frame[1] = _frame[1] & 0x7f;

?if (set) _frame[1] = _frame[1] | 0x80;

?}

?void SetPayloadType(unsigned char type) {

?if (_frameLen < 2)

?return;

?_frame[1] = _frame [1] & 0x80;

?_frame[1] = _frame [1] | (type & 0x7f);

?}

?unsigned char GetPayloadType() const

?{

?if (_frameLen < 1)

?return 0xff;

?return _frame[1] & 0x7f;

?}

?unsigned long GetTimestamp() const {

?if (_frameLen < 8)

?return 0;

?return ((_frame[4] << 24) + (_frame[5] << 16) + (_frame[6] << 8) + _frame[7]);

?}

?void SetTimestamp(unsigned long timestamp) {

?if (_frameLen < 8)

?return;

?_frame[4] = (unsigned char) ((timestamp >> 24) & 0xff);

?_frame[5] = (unsigned char) ((timestamp >> 16) & 0xff);

?_frame[6] = (unsigned char) ((timestamp >> 8) & 0xff);

?_frame[7] = (unsigned char) (timestamp & 0xff);

?};

protected:

?unsigned char* _frame;

?int _frameLen;

};

struct frameHeader {

?unsigned int?x;

?unsigned int?y;

?unsigned int?width;

?unsigned int?height;

};

#endif

原封不動(dòng)膘怕,可以直接拿來(lái)使用。當(dāng)然召庞,自己寫(xiě)一個(gè)也不麻煩岛心。很多人寫(xiě)不好估計(jì)是卡在位運(yùn)算上了。

然后篮灼,進(jìn)入video\MPEG4-ffmpeg目錄下看mpeg4.cxx忘古,這里包含了完整的RFC解包重組及MPEG4解碼的源碼。直接編譯可能通不過(guò)诅诱,好在代碼寫(xiě)的非常整齊髓堪,提取出來(lái)就是了。解包解碼只要看這一個(gè)函數(shù):

bool MPEG4DecoderContext::DecodeFrames(const BYTE * src, unsigned & srcLen,

?BYTE * dst, unsigned & dstLen,

?unsigned int & flags)

{

?if (!FFMPEGLibraryInstance.IsLoaded())

?return 0;

?// Creates our frames

?RTPFrame srcRTP(src, srcLen);

?RTPFrame dstRTP(dst, dstLen, RTP_DYNAMIC_PAYLOAD);

?dstLen = 0;

?flags = 0;

?int srcPayloadSize = srcRTP.GetPayloadSize();

?SetDynamicDecodingParams(true); // Adjust dynamic settings, restart allowed

?// Don't exceed buffer limits.?_encFrameLen set by ResizeDecodingFrame

?if(_lastPktOffset + srcPayloadSize < _encFrameLen)

?{

?// Copy the payload data into the buffer and update the offset

?memcpy(_encFrameBuffer + _lastPktOffset, srcRTP.GetPayloadPtr(),

?srcPayloadSize);

?_lastPktOffset += srcPayloadSize;

?}

?else {

?// Likely we dropped the marker packet, so at this point we have a

?// full buffer with some of the frame we wanted and some of the next

?// frame.

?//I'm on the fence about whether to send the data to the

?// decoder and hope for the best, or to throw it all away and start

?// again.

?// throw the data away and ask for an IFrame

?TRACE(1, "MPEG4\tDecoder\tWaiting for an I-Frame");

?_lastPktOffset = 0;

?flags = (_gotAGoodFrame ? PluginCodec_ReturnCoderRequestIFrame?: 0);

?_gotAGoodFrame = false;

?return 1;

?}

?// decode the frame if we got the marker packet

?int got_picture = 0;

?if (srcRTP.GetMarker()) {

?_frameNum++;

?int len = FFMPEGLibraryInstance.AvcodecDecodeVideo

?(_avcontext, _avpicture, &got_picture,

?_encFrameBuffer, _lastPktOffset);

?if (len >= 0 && got_picture) {

#ifdef LIBAVCODEC_HAVE_SOURCE_DIR

?if (DecoderError(_keyRefreshThresh)) {

?// ask for an IFrame update, but still show what we've got

?flags = (_gotAGoodFrame ? PluginCodec_ReturnCoderRequestIFrame?: 0);

?_gotAGoodFrame = false;

?}

#endif

?TRACE_UP(4, "MPEG4\tDecoder\tDecoded " << len << " bytes" << ", Resolution: " << _avcontext->width << "x" << _avcontext->height);

?// If the decoding size changes on us, we can catch it and resize

?if (!_disableResize

?&& (_frameWidth != (unsigned)_avcontext->width

?|| _frameHeight != (unsigned)_avcontext->height))

?{

?// Set the decoding width to what avcodec says it is

?_frameWidth?= _avcontext->width;

?_frameHeight = _avcontext->height;

?// Set dynamic settings (framesize), restart as needed

?SetDynamicDecodingParams(true);

?return true;

?}

?// it's stride time

?int frameBytes = (_frameWidth * _frameHeight * 3) / 2;

?PluginCodec_Video_FrameHeader * header

?= (PluginCodec_Video_FrameHeader *)dstRTP.GetPayloadPtr();

?header->x = header->y = 0;

?header->width = _frameWidth;

?header->height = _frameHeight;

?unsigned char *dstData = OPAL_VIDEO_FRAME_DATA_PTR(header);

?for (int i=0; i<3; i ++) {

?unsigned char *srcData = _avpicture->data[i];

?int dst_stride = i ? _frameWidth >> 1 : _frameWidth;

?int src_stride = _avpicture->linesize[i];

?int h = i ? _frameHeight >> 1 : _frameHeight;

?if (src_stride==dst_stride) {

?memcpy(dstData, srcData, dst_stride*h);

?dstData += dst_stride*h;

?}

?else

?{

?while (h--) {

?memcpy(dstData, srcData, dst_stride);

?dstData += dst_stride;

?srcData += src_stride;

?}

?}

?}

?// Treating the screen as an RTP is weird

?dstRTP.SetPayloadSize(sizeof(PluginCodec_Video_FrameHeader)

?+ frameBytes);

?dstRTP.SetPayloadType(RTP_DYNAMIC_PAYLOAD);

?dstRTP.SetTimestamp(srcRTP.GetTimestamp());

?dstRTP.SetMarker(true);

?dstLen = dstRTP.GetFrameLen();

?flags = PluginCodec_ReturnCoderLastFrame;

?_gotAGoodFrame = true;

?}

?else {

?TRACE(1, "MPEG4\tDecoder\tDecoded "<< len << " bytes without getting a Picture...");

?// decoding error, ask for an IFrame update

?flags = (_gotAGoodFrame ? PluginCodec_ReturnCoderRequestIFrame?: 0);

?_gotAGoodFrame = false;

?}

?_lastPktOffset = 0;

?}

?return true;

}

寫(xiě)的非常非常的明白:if (srcRTP.GetMarker())娘荡,到了這里表示收滿了一包干旁,開(kāi)始去解碼。

mpeg4-es的RFC還原重組就這么簡(jiǎn)單炮沐,下一步的解碼争群,就涉及到用libavcodec.dll了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市央拖,隨后出現(xiàn)的幾起案子祭阀,更是在濱河造成了極大的恐慌,老刑警劉巖鲜戒,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異抹凳,居然都是意外死亡遏餐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)赢底,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)失都,“玉大人,你說(shuō)我怎么就攤上這事幸冻〈馀樱” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵洽损,是天一觀的道長(zhǎng)庞溜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)碑定,這世上最難降的妖魔是什么流码? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任又官,我火速辦了婚禮,結(jié)果婚禮上漫试,老公的妹妹穿的比我還像新娘六敬。我一直安慰自己,他們只是感情好驾荣,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布外构。 她就那樣靜靜地躺著,像睡著了一般播掷。 火紅的嫁衣襯著肌膚如雪典勇。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,785評(píng)論 1 314
  • 那天叮趴,我揣著相機(jī)與錄音割笙,去河邊找鬼。 笑死眯亦,一個(gè)胖子當(dāng)著我的面吹牛伤溉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妻率,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼乱顾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了宫静?” 一聲冷哼從身側(cè)響起走净,我...
    開(kāi)封第一講書(shū)人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎孤里,沒(méi)想到半個(gè)月后伏伯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捌袜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年说搅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虏等。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弄唧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霍衫,到底是詐尸還是另有隱情候引,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布敦跌,位于F島的核電站澄干,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜傻寂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一息尺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疾掰,春花似錦搂誉、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至拂檩,卻和暖如春侮腹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背稻励。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工父阻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人望抽。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓加矛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親煤篙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子斟览,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • ## 可重入函數(shù) ### 可重入性的理解 若一個(gè)程序或子程序可以安全的被并行執(zhí)行,則稱其為可重入的辑奈;即當(dāng)該子程序正...
    夏至亦韻閱讀 715評(píng)論 0 0
  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom閱讀 2,701評(píng)論 0 3
  • 1苛茂、禁止手機(jī)睡眠 [UIApplication sharedApplication].idleTimerDisab...
    小小夕舞閱讀 1,465評(píng)論 1 1
  • ### YUV顏色空間 視頻是由一幀一幀的數(shù)據(jù)連接而成,而一幀視頻數(shù)據(jù)其實(shí)就是一張圖片鸠窗。 yuv是一種圖片儲(chǔ)存格式...
    天使君閱讀 3,303評(píng)論 0 4
  • 教程一:視頻截圖(Tutorial 01: Making Screencaps) 首先我們需要了解視頻文件的一些基...
    90后的思維閱讀 4,713評(píng)論 0 3