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了