之前介绍过自己直接使用RTP收发h.264数据,这样有一个麻烦就是RTP协议的各个参数需要自己一个一个位的去填充,这样不利于发送也不方便接收。jrtplib库就刚好解决了这样的麻烦,同时它还提供了很多RTCP的信息查询接口,这样为实现实时流控制提供了方便。
在本文中,将介绍h264 通过jrtplib库来实现实时的发送和接收。发送方发送,按照编码的习惯,我们习惯上是每完成一帧数据的编码就整一帧数据的发送。与此类似,在接收端,我们也是喜欢将整一帧的数据接收完整后再进行解码等处理。但是,这样会带来一个问题,我们网络最大传输单位MUT 一般是1400个字节,而我们进行h.264数据编码的时候,很多数据帧会大于1400帧,所以需要分开几个数据包来发送,在接收的时候,需要将分开的数据包再合成一个数据帧。
注明:
下面代码是在我电脑写的最简程序。
发送端IP:192.168.0.5
接收端IP:192.168.0.6
发送端口:6666
接收端口:6664
注意几点:
(1)jrtplib不能使用基数端口,基数端口用来建立RTCP
(2)jrtplib设置的默认MUT是1400,但是实际每包数据不能发送1400字节,因为它还有TCP 头数据
receive.c
/*=============================================================================
* FileName: receive.cpp
* Desc: reveive h.264 from RTP server
* Author: licaibiao
* LastChange: 2017-04-22
* =============================================================================*/
#include <jrtplib3/rtpsession.h>
#include <jrtplib3/rtpudpv4transmitter.h>
#include <jrtplib3/rtpipv4address.h>
#include <jrtplib3/rtpsessionparams.h>
#include <jrtplib3/rtperrors.h>
#include <jrtplib3/rtplibraryversion.h>
#include <jrtplib3/rtppacket.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace jrtplib;
void checkerror(int rtperr)
{
if (rtperr < 0)
{
std::cout << "ERROR: " << RTPGetErrorString(rtperr) << std::endl;
exit(-1);
}
}
int main(void)
{
RTPSession sess;
uint16_t portbase = 6664;
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);
checkerror(status);
sess.BeginDataAccess();
RTPTime delay(0.001);
RTPTime starttime = RTPTime::CurrentTime();
FILE *fd;
size_t len;
uint8_t *loaddata;
RTPPacket *pack;
uint8_t buff[1024*100] = {0};
int pos = 0;
fd = fopen("./test_recv.h264","wb+");
while (!done)
{
status = sess.Poll();
checkerror(status);
if (sess.GotoFirstSourceWithData())
{
do
{
while ((pack = sess.GetNextPacket()) != NULL)
{
loaddata = pack->GetPayloadData();
len = pack->GetPayloadLength();
if(pack->GetPayloadType() == 96) //H264
{
if(pack->HasMarker()) // the last packet
{
memcpy(&buff[pos],loaddata,len);
fwrite(buff, 1, pos+len, fd);
pos = 0;
}
else
{
memcpy(&buff[pos],loaddata,len);
pos = pos + len;
}
}else
{
printf("!!! GetPayloadType = %d !!!! \n ",pack->GetPayloadType());
}
sess.DeletePacket(pack);
}
} while (sess.GotoNextSourceWithData());
}
RTPTime::Wait(delay);
RTPTime t = RTPTime::CurrentTime();
t -= starttime;
if (t > RTPTime(40.0))
done = true;
}
fclose(fd);
sess.EndDataAccess();
delay = RTPTime(10.0);
sess.BYEDestroy(delay,0,0);
return 0;
}
发送端程序 sender
/*=============================================================================
* FileName: sender.cpp
* Desc: sending h.264 data to client
* Author: licaibiao
* LastChange: 2017-04-22
* =============================================================================*/
#include <jrtplib3/rtpsession.h>
#include <jrtplib3/rtpudpv4transmitter.h>
#include <jrtplib3/rtpipv4address.h>
#include <jrtplib3/rtpsessionparams.h>
#include <jrtplib3/rtperrors.h>
#include <jrtplib3/rtplibraryversion.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace jrtplib;
#define MAXLEN (RTP_DEFAULTPACKETSIZE - 100)
void checkerror(int rtperr){
if (rtperr < 0){
std::cout << "ERROR: " << RTPGetErrorString(rtperr) << std::endl;
exit(-1);
}
}
class MyRTPSession : public RTPSession{
public:
MyRTPSession(void);
~MyRTPSession(void);
void SendH264Nalu(RTPSession* sess,uint8_t* m_h264Buf,int buflen);
protected:
};
MyRTPSession::MyRTPSession(void){}
MyRTPSession::~MyRTPSession(void){}
int main(void)
{
int i;
int num;
int status;
RTPSession sess;
MyRTPSession sender;
uint16_t portbase = 6666;
uint16_t destport = 6664;
uint8_t destip[]={192,168,0,6};
RTPUDPv4TransmissionParams transparams;
RTPSessionParams sessparams;
/* set h264 param */
sessparams.SetUsePredefinedSSRC(true); //设置使用预先定义的SSRC
sessparams.SetOwnTimestampUnit(1.0/9000.0); /* 设置采样间隔 */
sessparams.SetAcceptOwnPackets(true); //接收自己发送的数据包
transparams.SetPortbase(portbase);
status = sess.Create(sessparams,&transparams);
checkerror(status);
RTPIPv4Address addr(destip,destport);
status = sess.AddDestination(addr);
checkerror(status);
sess.SetDefaultTimestampIncrement(3600);/* 设置时间戳增加间隔 */
sess.SetDefaultPayloadType(96);
sess.SetDefaultMark(true);
FILE *fd;
int pos = 0;
int header_flag = 0;
uint8_t buff[1024*100] = {0};
fd = fopen("./test.h264","rb");
fread(buff, 1, 4, fd);
if((buff[0]==0)&&(buff[1]==0)&&(buff[2]==0)&&(buff[3]==1)){
header_flag = 1;
pos = 4;
}else{
header_flag = 0;
pos = 3;
}
while((feof(fd)==0))
{
buff[pos++] = fgetc(fd);
if(header_flag == 1){ //00 00 00 01
if((buff[pos-1]==1)&&(buff[pos-2]==0)&&(buff[pos-3]==0)&&(buff[pos-4]==0)){
sender.SendH264Nalu(&sess, buff,pos-4);
buff[0] = 0x00;
buff[1] = 0x00;
buff[2] = 0x00;
buff[3] = 0x01;
pos = 4;
RTPTime::Wait(0.03);
}
}
else{
if((buff[pos-1]==1)&&(buff[pos-2]==0)&&(buff[pos-3]==0)){
sender.SendH264Nalu(&sess, buff, pos-3);
buff[0] = 0x00;
buff[1] = 0x00;
buff[3] = 0x01;
pos = 3;
RTPTime::Wait(0.03);
}
}
}
if(pos != 0){
sender.SendH264Nalu(&sess,buff,pos);
}
fclose(fd);
printf("end of the read\n");
sess.BYEDestroy(RTPTime(10,0),0,0);
return 0;
}
在上面的代码中,去掉了void SendH264Nalu(RTPSession* sess,uint8_t* m_h264Buf,int buflen) 函数的实现
完整的代码可以在这里下载:linux 使用jrtplib收发h.264视频文件