使用jrtplib库收发视频流
?jrtplib開(kāi)源庫(kù)可以用于設(shè)備端發(fā)視頻流, 也可用于服務(wù)器端收視頻流,在使用的過(guò)程也有幾項(xiàng)需要注意。
1. 編譯使用環(huán)境
1.1 客戶(hù)端:Cortex-A9 開(kāi)發(fā)板,,g++交叉編譯,插上攝像頭。
? ? ? ?這里jrtplib需要交叉編譯,前面文章有講,且編譯的時(shí)候,要將服務(wù)端和客戶(hù)端都設(shè)置為相同的大端或者小端,默認(rèn)的是采用大端模式,那么我這里約定使用大端模式,這點(diǎn)需要特別注意,否則對(duì)應(yīng)不上,收發(fā)不成功。具體怎么配置,參見(jiàn)cmake文檔。
如果漏掉配置了,就手動(dòng)在rtpconfig.h 加一句:
#define RTP_BIG_ENDIAN
? ? ?
1.2 服務(wù)端:Windows 10,vc++ 2012。
? ? jrtplib編譯成靜態(tài)庫(kù),文件名:jrtplib_d.lib
?? ?另外,同樣也需要編譯成大端模式,在rtpconfig.h中加上:
?? ?#define RTP_BIG_ENDIAN
?
? ? ? ?這里說(shuō)一下,我是如何發(fā)現(xiàn)這個(gè)問(wèn)題的呢?其實(shí)也花費(fèi)了我一點(diǎn)精力,這就是開(kāi)源的好處,可以深入代碼調(diào)試,如果不是開(kāi)源的,估計(jì)就耗死在這里,在此再一次佩服開(kāi)源的偉大。我們做開(kāi)發(fā),不是只知道會(huì)用就行了,重要的是要知道怎么分析問(wèn)題解決問(wèn)題。
? ? 下面就深入代碼分析一下:
?? ?找到解析rtp包的地方:rtppacket.cpp,找到函數(shù):int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack),
?? ?這其中有如下一段代碼:
?? ?packetbytes = (uint8_t *)rawpack.GetData();
?? ?rtpheader = (RTPHeader *)packetbytes;
?? ?
?? ?// The version number should be correct
?? ?if (rtpheader->version != RTP_VERSION)
?? ??? ?return ERR_RTP_PACKET_INVALIDPACKET;? ??
?? ?在這兒判斷版本號(hào):rtpheader->version
?? ?我一看這兒rtpheader->version==0,而RTP_VERSION是2,再看rtpheader的內(nèi)存內(nèi)容:
?? ?0x00BEB7F8 ?02 c1 5e 58 9c b3 ff d3 61 a8 24 ec ff d8 ff ...
?? ?0x00BEB7F8為rtpheader的內(nèi)存地址,這樣來(lái)看,明明頭部是等于2呀,為什么解析出來(lái)不對(duì)呢?
?? ?再看看頭的定義:
?? ?struct RTPHeader
?? ?{
?? ?#ifdef RTP_BIG_ENDIAN
?? ??? ?uint8_t version:2;
?? ??? ?uint8_t padding:1;
?? ??? ?uint8_t extension:1;
?? ??? ?uint8_t csrccount:4;
?? ??? ?
?? ??? ?uint8_t marker:1;
?? ??? ?uint8_t payloadtype:7;
?? ?#else // little endian
?? ??? ?uint8_t csrccount:4;
?? ??? ?uint8_t extension:1;
?? ??? ?uint8_t padding:1;
?? ??? ?uint8_t version:2;
?? ??? ?
?? ??? ?uint8_t payloadtype:7;
?? ??? ?uint8_t marker:1;
?? ?#endif // RTP_BIG_ENDIAN
?? ??? ?
?? ??? ?uint16_t sequencenumber;
?? ??? ?uint32_t timestamp;
?? ??? ?uint32_t ssrc;
?? ?};
?? ?
? ? 看這兒,如果是RTP_BIG_ENDIAN,數(shù)就不就對(duì)了嗎?這樣問(wèn)題解決。
?? ?
2. 客戶(hù)端發(fā)送視頻流
? ? ? ?視頻流是抓取的MPEG4數(shù)據(jù),所以動(dòng)態(tài)圖片比較大。這里需要說(shuō)明的是,jrtplib不會(huì)幫你分包,一次發(fā)送的數(shù)據(jù)包不能超過(guò)大約1440字節(jié),最好比1440字節(jié)小一點(diǎn),否則認(rèn)為數(shù)據(jù)包太大,發(fā)送失敗。所以只能自己分包發(fā)送,其它方法暫時(shí)沒(méi)有試過(guò)。另外一點(diǎn)就是需要特別注意,既然自己分包,那么如何表示開(kāi)始,如何表示結(jié)束呢?這里需要用到頭部的marker字段,對(duì)于marker的定義,是這么說(shuō)的:對(duì)于視頻,標(biāo)記一幀的結(jié)束;對(duì)于音頻,標(biāo)記會(huì)話(huà)的開(kāi)始。
下面是客戶(hù)端關(guān)鍵代碼:
?? ?/*Create rtp session*/
?? ?RTPSession rtpsess;
?? ?unsigned long server_ip = inet_addr("192.168.1.7"); ?//服務(wù)端地址
?? ?
?? ?//convert the ip address to be network byte order.
?? ?server_ip = htonl(server_ip);
?? ?
?? ?//here port is not required.
?? ?unsigned short port = 8888; ?//服務(wù)端接收地址,注意,必須是偶數(shù),因?yàn)榭肯乱晃黄鏀?shù)端口(這兒是8889)為rtcp使用。
?? ?
?? ?int port_base = 6000;//本端地址,也必須是偶數(shù),6001為rtcp使用
?? ?RTPSessionParams rtppara;
?? ?rtppara.SetOwnTimestampUnit(1.0/10.0);?? ?
?? ?rtppara.SetAcceptOwnPackets(true);
?? ?
?? ?//這個(gè)參數(shù)好像與分包發(fā)送的最大包沒(méi)有關(guān)系,但是最大不能超過(guò)65535.
?? ?rtppara.SetMaximumPacketSize(60000+128); ??
?? ?
?? ?
?? ?RTPUDPv4TransmissionParams transparams;
?? ?transparams.SetPortbase(port_base);
?? ?
?? ?int status = rtpsess.Create(rtppara,&transparams);
?? ?checkerror(status);
?? ?
?? ?RTPIPv4Address rtpaddr(server_ip,port);?? ?
?? ?status = rtpsess.AddDestination(rtpaddr);
? ? checkerror(status);
?? ?
?? ?//Add here 2 statements can cause sending data to be failing.
?? ?rtpsess.SetDefaultPayloadType(96);
?? ?
?? ?/*這里設(shè)置為true或false都是沒(méi)有關(guān)系的,但是必須要調(diào)用,和后面marker沒(méi)有關(guān)系,
?? ?看定義:
?? ?inline int RTPPacketBuilder::SetDefaultMark(bool m)
?? ?{
?? ??? ?if (!init)
?? ??? ??? ?return ERR_RTP_PACKBUILD_NOTINIT;
?? ??? ?defmarkset = true;
?? ??? ?defaultmark = m;
?? ??? ?return 0;
?? ?}
?? ?
?? ?如果調(diào)用了這個(gè)函數(shù),defmarkset為置為true,這和后面的組包是有關(guān)系的,看組包定義:
?? ?int RTPPacketBuilder::BuildPacket(const void *data,size_t len)
?? ?{
?? ??? ?if (!init)
?? ??? ??? ?return ERR_RTP_PACKBUILD_NOTINIT;
?? ??? ?if (!defptset)
?? ??? ??? ?return ERR_RTP_PACKBUILD_DEFAULTPAYLOADTYPENOTSET;
?? ??? ?if (!defmarkset)
?? ??? ??? ?return ERR_RTP_PACKBUILD_DEFAULTMARKNOTSET; ?//這兒,未定義直接返回錯(cuò)誤了。
?? ??? ?if (!deftsset)
?? ??? ??? ?return ERR_RTP_PACKBUILD_DEFAULTTSINCNOTSET;
?? ??? ?return PrivateBuildPacket(data,len,defaultpayloadtype,defaultmark,defaulttimestampinc,false);
?? ?}
?? ?
?? ?*/
? ??? ?rtpsess.SetDefaultMark(false); ?
?? ?unsigned char databuffer[60000] = {0};?? ?
?? ?rtpsess.SetDefaultTimestampIncrement(10);
?? ?for(;;)
?? ?{
?? ??? ?if(count>500)//抓滿(mǎn)500張退出
?? ??? ?{
?? ??? ??? ?break;
?? ??? ?}
?? ? ? ??
? ? ? ? ? ? //抓取視頻,MPEG4格式
? ? ? ? ? ? ret = avobj.GetFrame(¶meter);?? ??? ??? ??? ?
?? ??? ??? ?//split buffer into many small packets.
?? ??? ??? ?int packsize = 1000; //一包1000字節(jié)
?? ??? ??? ?int leftsize = parameter.v4l2Info.length%packsize; ? //整包后余下的字節(jié)
?? ??? ??? ?int intcnt = parameter.v4l2Info.length/packsize;?? ?//整數(shù)包數(shù)?? ?? ? ? ? ? ?
?? ??? ?
?? ??? ??? ?//begin to send a frame?
?? ??? ??? ?for(int i=0;i<intcnt;i++)
?? ??? ??? ?{
?? ??? ??? ??? ?memset((void*)databuffer,0,packsize);
?? ??? ??? ??? ?memcpy((void*)databuffer,((unsigned char*)parameter.v4l2Info.buffer)+i*packsize,packsize);?? ?
?? ??? ??? ??? ?//這兒是我的圖片尾部是空白,其實(shí)數(shù)據(jù)沒(méi)有這么大,如果下一包頭部全是0,其實(shí)圖片數(shù)據(jù)已經(jīng)結(jié)束了,應(yīng)該
?? ??? ??? ??? ?//告訴服務(wù)端我已發(fā)完一幀圖片
?? ??? ??? ??? ?if(databuffer[0] == 0 && databuffer[1] == 0 && databuffer[2] == 0)?
?? ??? ??? ??? ?{? ? ? ? ? ? ? ??
?? ??? ??? ??? ??? ?//this is the last frame,the marker must be true.
?? ??? ??? ??? ??? ?//注意這兒的第4個(gè)參數(shù),true表示發(fā)完一幀圖片了。
?? ??? ??? ??? ??? ?status = rtpsess.SendPacket((void*)databuffer,packsize,96,true,10);
?? ??? ??? ??? ??? ?checkerror(status);?? ?? ? ? ? ? ? ? ??
?? ??? ??? ??? ??? ?break;
?? ??? ??? ??? ?}
?? ??? ??? ??? ?else
?? ??? ??? ??? ?{
?? ??? ??? ??? ??? ?//print(databuffer,packsize);
?? ??? ??? ??? ??? ?//pay attention to the marker,here it must be false.
?? ??? ??? ??? ??? ?
?? ??? ??? ??? ??? ?//注意這兒的第4個(gè)參數(shù),false表示未發(fā)完一幀,還會(huì)繼續(xù)發(fā)數(shù)據(jù)體。
?? ??? ??? ??? ??? ?status = rtpsess.SendPacket((void*)databuffer,packsize,96,false,10);?? ?
?? ??? ??? ??? ??? ?checkerror(status);?? ??? ?
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ??? ?
?? ??? ??? ?//send left data
?? ??? ??? ?printf("the left size is:%d\n",leftsize);
?? ??? ??? ?if(leftsize>0)
?? ??? ??? ?{
?? ??? ??? ??? ?memset((void*)databuffer,0,packsize);
?? ??? ??? ??? ?memcpy((void*)databuffer,((unsigned char*)parameter.v4l2Info.buffer)+packsize*intcnt,leftsize);
?? ??? ??? ??? ?if(databuffer[0] != 0 && databuffer[1] != 0 && databuffer[2] != 0)
?? ??? ??? ??? ?{
?? ??? ??? ??? ??? ?
?? ??? ??? ??? ??? ?//this is the last frame,the marker must be true.
?? ??? ??? ??? ??? ?status = rtpsess.SendPacket((void*)databuffer,leftsize,96,true,10);
?? ??? ??? ??? ??? ?checkerror(status);?? ??? ?
?? ??? ??? ??? ?}
?? ??? ??? ??? ?else
?? ??? ??? ??? ?{
?? ??? ??? ??? ??? ?printf("the last data buffer is 0\n");?? ??? ??? ??? ??? ?
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ??? ?RTPTime::Wait(RTPTime(0,100));?? ?
?? ??? ?}?? ??? ?
?? ?
?? ??? ?count++;
?? ?}
?? ?
3. 服務(wù)端接收視頻流
? ?服務(wù)端接收關(guān)鍵代碼,放在一個(gè)線(xiàn)程中執(zhí)行:
? ?UINT CVideoMonitorDlg::RTPServerProc(LPVOID lParam)
? ?{
?? ??? ?CVideoMonitorDlg* pThis = (CVideoMonitorDlg*)lParam;
?? ??? ?RTPSession sess;
?? ??? ?uint16_t portbase = 8888;
?? ??? ?int status;
?? ??? ?bool done = false;
?? ??? ?RTPUDPv4TransmissionParams transparams;
?? ??? ?RTPSessionParams sessparams;
?? ??? ?sessparams.SetOwnTimestampUnit(1.0 / 10.0);
?? ??? ?sessparams.SetAcceptOwnPackets(true);
?? ??? ?transparams.SetPortbase(portbase);
?? ??? ?status = sess.Create(sessparams, &transparams);
?? ??? ?pThis->checkerror(status);
?? ??? ?sess.SetDefaultTimestampIncrement(10);
?? ??? ?sess.SetDefaultMark(true);
?? ??? ?int count = 1;
?? ??? ?//sess.BeginDataAccess();
?? ??? ?RTPTime delay(0.020);
?? ??? ?RTPTime starttime = RTPTime::CurrentTime();
? ? ? ??
?? ??? ?//打開(kāi)視頻存儲(chǔ)文件,這里我存儲(chǔ)為avi文件。
?? ??? ?pThis->OpenVideoFile();
?? ??? ?while (!done)
?? ??? ?{
?? ??? ??? ?//如果沒(méi)有使用jthread,那就要調(diào)用這個(gè)函數(shù),這里我不使用jthread.
?? ??? ??? ?//這一步,其實(shí)質(zhì)是從socket取出數(shù)據(jù)并放入緩沖區(qū)。
?? ??? ??? ?status = sess.Poll();
?? ??? ??? ?pThis->checkerror(status);
?? ??? ??? ?
?? ??? ??? ?//這里是加鎖
?? ??? ??? ?sess.BeginDataAccess();
?? ??? ??? ?//開(kāi)始處理緩沖區(qū)中的數(shù)據(jù)
?? ??? ??? ?if (sess.GotoFirstSourceWithData())
?? ??? ??? ?{
?? ??? ??? ??? ?do
?? ??? ??? ??? ?{
?? ??? ??? ??? ??? ?RTPPacket *pack;
?? ??? ??? ??? ??? ?while ((pack = sess.GetNextPacket()) != NULL)
?? ??? ??? ??? ??? ?{?? ??? ?
? ? ? ? ? ? ? ? ? ? ? ? //取出數(shù)據(jù)加入我的緩存區(qū)?? ??? ??? ??? ??? ?
?? ??? ??? ??? ??? ??? ?pThis->AddDataPacket(pack->GetPayloadData(),pack->GetPayloadLength());
?? ??? ??? ??? ??? ??? ?
?? ??? ??? ??? ??? ??? ?//注意了,這兒就是前面的SendPacket的第4個(gè)參數(shù),
?? ??? ??? ??? ??? ??? ?bool hasmarker = pack->HasMarker();
?? ??? ??? ??? ??? ??? ?if(hasmarker)
?? ??? ??? ??? ??? ??? ?{
?? ??? ??? ??? ??? ??? ? ? ?//表示收完一幀圖片,寫(xiě)入文件
?? ??? ??? ??? ??? ??? ??? ?pThis->WriteVideoFile(count);?? ??? ??? ??? ??? ??? ?
?? ??? ??? ??? ??? ??? ??? ?count++;
?? ??? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ??? ?sess.DeletePacket(pack);
?? ??? ??? ??? ??? ?}
?? ??? ??? ??? ?
?? ??? ??? ??? ?} while (sess.GotoNextSourceWithData());
?? ??? ??? ?}
?? ??? ??? ?sess.EndDataAccess();
?? ??? ??? ?RTPTime::Wait(delay);
?? ??? ??? ?RTPTime t = RTPTime::CurrentTime();
?? ??? ??? ?t -= starttime;
?? ??? ??? ?if (t > RTPTime(60000.0))
?? ??? ??? ??? ?done = true;
?? ??? ??? ?if(count>490)
?? ??? ??? ?{
?? ??? ??? ? ? ?//收完490幀圖片,關(guān)閉視頻文件.
?? ??? ??? ??? ?pThis->CloseVideoFile();?? ??? ?
?? ??? ??? ?}
?? ??? ?}
?? ??? ?delay = RTPTime(10.0);
?? ??? ?sess.BYEDestroy(delay, 0, 0);
?? ??? ?
?? ??? ?return 0;
?? ?}
?? ?
?? ?經(jīng)過(guò)實(shí)測(cè),接收存儲(chǔ)的avi文件可以播放。
??
總結(jié)
以上是生活随笔為你收集整理的使用jrtplib库收发视频流的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ESP32黑客帝国数字雨动画,矩阵它来了
- 下一篇: java关键字匹配算法_简单关键词匹配算