上篇GB28181实现已经将实现sip所需要用到的三方库,以及一些注意点描述了。由于B接口与281都是基于sip,实现方式差不多,这里就不再描述。可以将GB28181实现_LinuxZQ的博客-CSDN博客_gb28181 实现 文章sip部分作为补充学习。本篇仅描述难点部分,也就是验收的硬性要求,视频预览着重讲解
因手上只有B接口2014的验收软件,所以本篇以2014标准进行讲解。2019协议也是增加了tcp支持,与281类型,简单扩展就可以实现
视频预览
与281一致,同样是基于RTP流的传输,交互流程也一致,都是基于SIP INVITE+SDP。
支持的RTP Payload如下
这里的Payload值定义与281里面还是有区别的,B接口种的264为100,我们也是以这种方式向平台进行推流
2014种的视频以udp为通道传输,所以先创建udp通道。
bool CRtpVideo::rtpTrans::setupUDP(int localPort, const std::string &remoteIp, int remotePort, int payloadType, uint32_t ssrc)
{
RTPUDPv4TransmissionParams transParams;
RTPSessionParams sessParams;
sessParams.SetOwnTimestampUnit(1.0 / 90000.0);
// sessParams.SetAcceptOwnPackets(true);
sessParams.SetUsePredefinedSSRC(true); //设置使用预先定义的SSRC
sessParams.SetPredefinedSSRC(ssrc);
transParams.SetPortbase(localPort);
int ret = sess.Create(sessParams, &transParams);
if (ret < 0)
{
errorf("setup rtp[%s:%d] failed, msg[%s]\n", remoteIp.c_str(), remotePort, RTPGetErrorString(ret).c_str());
return false;
}
infof("payType is %d, ssrc[%u], remoteIP[%s], remotePort[%d]\n", payloadType, ssrc, remoteIp.c_str(), remotePort);
sess.SetDefaultPayloadType(payloadType);//设置传输类型
sess.SetDefaultMark(true); //设置位
sess.SetTimestampUnit(1.0 / 90000.0); //设置采样间隔
sess.SetDefaultTimestampIncrement(3600);//设置时间戳增加间隔
uint32_t destip;
destip = inet_addr(remoteIp.c_str());
if (destip == INADDR_NONE)
{
errorf("bad ip[%s]\n", remoteIp.c_str());
return false;
}
destip = ntohl(destip);
RTPIPv4Address addr(destip, remotePort);
ret = sess.AddDestination(addr);
if (ret < 0)
{
errorf("add dest[%s:%d] failed, masg[%s]\n", remoteIp.c_str(), remotePort, RTPGetErrorString(ret).c_str());
return false;
}
H264 = payloadType;
return true;
}
通道搭建成功后,就可以着手推流了,由于仅是H264裸流推送,所以不需要额外的封装。但是考虑到MTU的大小,需要进行分包。协议也明确了,需要进行分包,参考multi-slice或者FU-A。我们选择FU-A的方式。这个实现,网上也是有很多代码,示例如下
/// 因264的流开头可能为00 00 00 01,也有可能为00 00 01,所以需要动态找到nalu头
int CRtpVideo::rtpTrans::findNalu(uint8_t *data)
{
char code[3] = {0x0, 0x0, 0x1};
char code1[4] = {0x0, 0x0, 0x0, 0x1};
bool bFind = true;
for (int i = 0; i < 3; i++)
{
if (data[i] != code[i])
{
bFind = false;
}
}
if (bFind)
{
return 3;
}
bFind = true;
for (int i = 0; i < 4; i++)
{
if (data[i] != code1[i])
{
bFind = false;
}
}
if (bFind)
{
return 4;
}
return -1;
}
bool CRtpVideo::rtpTrans::sendNALU(uint8_t *data, int len)
{
int posN = findNalu(data);
if (posN == -1)
{
warnf("not valid h264\n");
return false;
}
unsigned char *pSendbuf = &data[posN]; //发送数据指针
len = len - posN;
char sendbuf[1500]; //发送的数据缓冲
memset(sendbuf, 0, 1500);
int ret = 0;
/// MAX_RTP_PKT_LENTH 此处定义为1360,小于这个值的直接发送
if ( len <= MAX_RTP_PKT_LENGTH )
{
// infof("min size is %d\n", len);
ret = sess.SendPacket(pSendbuf, len, H264, true, 3600);
if (ret < 0)
{
errorf("send pkt failed, msg[%s]\n", RTPGetErrorString(ret).c_str());
return false;
}
}
else if(len > MAX_RTP_PKT_LENGTH)
{
//设置标志位Mark为0
sess.SetDefaultMark(false);
//printf("buflen = %d\n",buflen);
//得到该需要用多少长度为MAX_RTP_PKT_LENGTH字节的RTP包来发送
int k = 0, l = 0;
len = len - 1;
k = len / MAX_RTP_PKT_LENGTH;
l = len % MAX_RTP_PKT_LENGTH;
int t = 0; //用指示当前发送的是第几个分片RTP包
char nalHeader = pSendbuf[0]; // NALU 头
while( t < k || ( t == k && l > 0 ) )
{
if( (0 == t ) || ( t < k && 0 != t ) ) //第一包到最后包的前一包
{
sendbuf[0] = (nalHeader & 0x60) | 28;
sendbuf[1] = (nalHeader & 0x1f);
if ( 0 == t )
{
sendbuf[1] |= 0x80;
}
memcpy(sendbuf + 2, &pSendbuf[t * MAX_RTP_PKT_LENGTH + 1], MAX_RTP_PKT_LENGTH);
// infof("select size is %d\n", MAX_RTP_PKT_LENGTH + 2);
/// 分包的时间戳不要增加
ret = sess.SendPacket((void *)sendbuf, MAX_RTP_PKT_LENGTH + 2, H264, false, 0);
if (ret < 0)
{
errorf("send pkt failed, msg[%s]\n", RTPGetErrorString(ret).c_str());
return false;
}
t++;
memset(sendbuf, 0, 1500);
}
//最后一包
else if( ( k == t && l > 0 ) || ( t == (k - 1) && l == 0 ))
{
//设置标志位Mark为1
sess.SetDefaultMark(true);
int iSendLen;
if ( l > 0)
{
iSendLen = len - t * MAX_RTP_PKT_LENGTH;
}
else
{
iSendLen = MAX_RTP_PKT_LENGTH;
}
sendbuf[0] = (nalHeader & 0x60) | 28;
sendbuf[1] = (nalHeader & 0x1f);
sendbuf[1] |= 0x40;
memcpy(sendbuf + 2, &pSendbuf[t * MAX_RTP_PKT_LENGTH + 1], iSendLen);
// infof("last size is %d\n", iSendLen + 2);
/// 最后一包时间戳再增加
ret = sess.SendPacket((void *)sendbuf, iSendLen + 2, H264, true, 3600);
if (ret < 0)
{
errorf("send pkt failed, msg[%s]\n", RTPGetErrorString(ret).c_str());
return false;
}
t++;
memset(sendbuf, 0, 1500);
}
}
}
return true;
}
基本上实现到此处,B接口的流已经可以推送了。264流的来源,通过ffmpeg的avpacket即可获取,代码如下
void CRtpVideo::process()
{
AVPacket *packet = NULL;
infof("process GB video thread enter\n");
while (m_thread.looping())
{
if (!m_quePkt.recvMessage(packet, 1000))
{
continue;
}
if (m_bTrans)
{
if (m_flowType == "PS")
{
sendPS(packet);
}
else if (m_flowType == "H264")
{
sendH264(packet);
}
}
av_packet_unref(packet);
av_packet_free(&packet);
}
}
void CRtpVideo::sendH264(AVPacket *&packet)
{
if (m_bSendIframe)
{
if (packet->flags == AV_PKT_FLAG_KEY)
{
m_rtpTrans.sendNALU(packet->data, packet->size);
m_bSendIframe = false;
infof("send iframe\n");
}
}
else
{
m_rtpTrans.sendNALU(packet->data, packet->size);
}
}
demo程序已经和281程序整合在一起,地址如下
https://download.csdn.net/download/z5201314100/85271657
没有积分可进行百度网盘下载,路径如下:
链接:https://pan.baidu.com/s/1LbGs9MXVXXEIBNmfL_AkyQ
提取码:4cp5
二次开发接口及程序免费运行license请联系微信HardAndBetter获取,或者加入QQ群586166104讨论。
B接口平台测试软件ABDemo,下载地址如下
https://download.csdn.net/download/z5201314100/85573201
链接:https://pan.baidu.com/s/1NIg2imgrlKq5ftbKVaujYA
提取码:wnv2