使用jrtplib库收发视频流
?jrtplib開源庫可以用于設備端發視頻流, 也可用于服務器端收視頻流,在使用的過程也有幾項需要注意。
1. 編譯使用環境
1.1 客戶端:Cortex-A9 開發板,,g++交叉編譯,插上攝像頭。
? ? ? ?這里jrtplib需要交叉編譯,前面文章有講,且編譯的時候,要將服務端和客戶端都設置為相同的大端或者小端,默認的是采用大端模式,那么我這里約定使用大端模式,這點需要特別注意,否則對應不上,收發不成功。具體怎么配置,參見cmake文檔。
如果漏掉配置了,就手動在rtpconfig.h 加一句:
#define RTP_BIG_ENDIAN
? ? ?
1.2 服務端:Windows 10,vc++ 2012。
? ? jrtplib編譯成靜態庫,文件名:jrtplib_d.lib
?? ?另外,同樣也需要編譯成大端模式,在rtpconfig.h中加上:
?? ?#define RTP_BIG_ENDIAN
?
? ? ? ?這里說一下,我是如何發現這個問題的呢?其實也花費了我一點精力,這就是開源的好處,可以深入代碼調試,如果不是開源的,估計就耗死在這里,在此再一次佩服開源的偉大。我們做開發,不是只知道會用就行了,重要的是要知道怎么分析問題解決問題。
? ? 下面就深入代碼分析一下:
?? ?找到解析rtp包的地方:rtppacket.cpp,找到函數: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;? ??
?? ?在這兒判斷版本號:rtpheader->version
?? ?我一看這兒rtpheader->version==0,而RTP_VERSION是2,再看rtpheader的內存內容:
?? ?0x00BEB7F8 ?02 c1 5e 58 9c b3 ff d3 61 a8 24 ec ff d8 ff ...
?? ?0x00BEB7F8為rtpheader的內存地址,這樣來看,明明頭部是等于2呀,為什么解析出來不對呢?
?? ?再看看頭的定義:
?? ?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,數就不就對了嗎?這樣問題解決。
?? ?
2. 客戶端發送視頻流
? ? ? ?視頻流是抓取的MPEG4數據,所以動態圖片比較大。這里需要說明的是,jrtplib不會幫你分包,一次發送的數據包不能超過大約1440字節,最好比1440字節小一點,否則認為數據包太大,發送失敗。所以只能自己分包發送,其它方法暫時沒有試過。另外一點就是需要特別注意,既然自己分包,那么如何表示開始,如何表示結束呢?這里需要用到頭部的marker字段,對于marker的定義,是這么說的:對于視頻,標記一幀的結束;對于音頻,標記會話的開始。
下面是客戶端關鍵代碼:
?? ?/*Create rtp session*/
?? ?RTPSession rtpsess;
?? ?unsigned long server_ip = inet_addr("192.168.1.7"); ?//服務端地址
?? ?
?? ?//convert the ip address to be network byte order.
?? ?server_ip = htonl(server_ip);
?? ?
?? ?//here port is not required.
?? ?unsigned short port = 8888; ?//服務端接收地址,注意,必須是偶數,因為靠下一位奇數端口(這兒是8889)為rtcp使用。
?? ?
?? ?int port_base = 6000;//本端地址,也必須是偶數,6001為rtcp使用
?? ?RTPSessionParams rtppara;
?? ?rtppara.SetOwnTimestampUnit(1.0/10.0);?? ?
?? ?rtppara.SetAcceptOwnPackets(true);
?? ?
?? ?//這個參數好像與分包發送的最大包沒有關系,但是最大不能超過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);
?? ?
?? ?/*這里設置為true或false都是沒有關系的,但是必須要調用,和后面marker沒有關系,
?? ?看定義:
?? ?inline int RTPPacketBuilder::SetDefaultMark(bool m)
?? ?{
?? ??? ?if (!init)
?? ??? ??? ?return ERR_RTP_PACKBUILD_NOTINIT;
?? ??? ?defmarkset = true;
?? ??? ?defaultmark = m;
?? ??? ?return 0;
?? ?}
?? ?
?? ?如果調用了這個函數,defmarkset為置為true,這和后面的組包是有關系的,看組包定義:
?? ?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; ?//這兒,未定義直接返回錯誤了。
?? ??? ?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)//抓滿500張退出
?? ??? ?{
?? ??? ??? ?break;
?? ??? ?}
?? ? ? ??
? ? ? ? ? ? //抓取視頻,MPEG4格式
? ? ? ? ? ? ret = avobj.GetFrame(¶meter);?? ??? ??? ??? ?
?? ??? ??? ?//split buffer into many small packets.
?? ??? ??? ?int packsize = 1000; //一包1000字節
?? ??? ??? ?int leftsize = parameter.v4l2Info.length%packsize; ? //整包后余下的字節
?? ??? ??? ?int intcnt = parameter.v4l2Info.length/packsize;?? ?//整數包數?? ?? ? ? ? ? ?
?? ??? ?
?? ??? ??? ?//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);?? ?
?? ??? ??? ??? ?//這兒是我的圖片尾部是空白,其實數據沒有這么大,如果下一包頭部全是0,其實圖片數據已經結束了,應該
?? ??? ??? ??? ?//告訴服務端我已發完一幀圖片
?? ??? ??? ??? ?if(databuffer[0] == 0 && databuffer[1] == 0 && databuffer[2] == 0)?
?? ??? ??? ??? ?{? ? ? ? ? ? ? ??
?? ??? ??? ??? ??? ?//this is the last frame,the marker must be true.
?? ??? ??? ??? ??? ?//注意這兒的第4個參數,true表示發完一幀圖片了。
?? ??? ??? ??? ??? ?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個參數,false表示未發完一幀,還會繼續發數據體。
?? ??? ??? ??? ??? ?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. 服務端接收視頻流
? ?服務端接收關鍵代碼,放在一個線程中執行:
? ?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();
? ? ? ??
?? ??? ?//打開視頻存儲文件,這里我存儲為avi文件。
?? ??? ?pThis->OpenVideoFile();
?? ??? ?while (!done)
?? ??? ?{
?? ??? ??? ?//如果沒有使用jthread,那就要調用這個函數,這里我不使用jthread.
?? ??? ??? ?//這一步,其實質是從socket取出數據并放入緩沖區。
?? ??? ??? ?status = sess.Poll();
?? ??? ??? ?pThis->checkerror(status);
?? ??? ??? ?
?? ??? ??? ?//這里是加鎖
?? ??? ??? ?sess.BeginDataAccess();
?? ??? ??? ?//開始處理緩沖區中的數據
?? ??? ??? ?if (sess.GotoFirstSourceWithData())
?? ??? ??? ?{
?? ??? ??? ??? ?do
?? ??? ??? ??? ?{
?? ??? ??? ??? ??? ?RTPPacket *pack;
?? ??? ??? ??? ??? ?while ((pack = sess.GetNextPacket()) != NULL)
?? ??? ??? ??? ??? ?{?? ??? ?
? ? ? ? ? ? ? ? ? ? ? ? //取出數據加入我的緩存區?? ??? ??? ??? ??? ?
?? ??? ??? ??? ??? ??? ?pThis->AddDataPacket(pack->GetPayloadData(),pack->GetPayloadLength());
?? ??? ??? ??? ??? ??? ?
?? ??? ??? ??? ??? ??? ?//注意了,這兒就是前面的SendPacket的第4個參數,
?? ??? ??? ??? ??? ??? ?bool hasmarker = pack->HasMarker();
?? ??? ??? ??? ??? ??? ?if(hasmarker)
?? ??? ??? ??? ??? ??? ?{
?? ??? ??? ??? ??? ??? ? ? ?//表示收完一幀圖片,寫入文件
?? ??? ??? ??? ??? ??? ??? ?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幀圖片,關閉視頻文件.
?? ??? ??? ??? ?pThis->CloseVideoFile();?? ??? ?
?? ??? ??? ?}
?? ??? ?}
?? ??? ?delay = RTPTime(10.0);
?? ??? ?sess.BYEDestroy(delay, 0, 0);
?? ??? ?
?? ??? ?return 0;
?? ?}
?? ?
?? ?經過實測,接收存儲的avi文件可以播放。
??
總結
以上是生活随笔為你收集整理的使用jrtplib库收发视频流的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ESP32黑客帝国数字雨动画,矩阵它来了
- 下一篇: python语言和汇编语言_python