简介
这里实现客户端连接IP摄像头。
关于RTSP/RTP/RTCP的协议概念这里不做多的介绍,网上有很多很好很详细的讲解。这里主要弄清楚3点:
1:RTSP:负责服务器与客户端之间的请求与响应
2:RTP: 负责传输媒体数据
3:RTCP:在RTP传输过程中提供传输信息
RTSP
首相要创建TCP或者UDP连接。我使用的是TCP连接。
HI_S32 HI_SocketConnect(const HI_PCHAR pServer, HI_U16 Port, HI_U32 s32CntTimeout, HI_SOCKET *pSessSock)
{
HI_S32 GetHost = HI_FAILURE;
struct sockaddr_in SockAddr;
HI_U32 net_addr = 0;
GetHost=HI_Gethostbyname(pServer,Port,&SockAddr.sin_addr);
if(GetHost!=HI_SUCCESS)
{
net_addr = inet_addr(pServer);
if(INADDR_NONE == net_addr)
{
GetHost = HI_FAILURE;
}
else
{
GetHost = HI_SUCCESS;
SockAddr.sin_addr.s_addr=net_addr;
}
}
if(HI_SUCCESS == GetHost)
{
SockAddr.sin_family = AF_INET;
SockAddr.sin_port = htons(Port);
*pSessSock = HI_OpenTcp((struct sockaddr *) &SockAddr, sizeof(SockAddr), s32CntTimeout);
if (*pSessSock < 0)
{
//LogErrorInfo("HI_OpenTcp error!\n", strlen("HI_OpenTcp error!\n"));
GetHost = HI_FAILURE;
}
}
if(GetHost != HI_SUCCESS)
;//LogErrorInfo("connect error!\n", strlen("connect error!\n"));
return GetHost;
}
创建好连接后,按照RTSP协议,依次发送OPTIONS、DESCRIBE、SETUP、PLAY等包。关于如何构建这些包,可以使用抓包软件和VLC播放器。抓取RTSP包做参考。
按照上图构建
HI_S32 HI_Nvr_Rtsp_Options(HI_U32 u32Channel, HI_Nvr_Rtsp_Stream * pRtspStream) /*RTSP请求*/
{
HI_S32 s32Ret;
HI_U32 u32RecvLen = HI_NVR_MAX_BUF_LEN;
HI_CHAR szSendBuf[HI_NVR_MAX_BUF_LEN] = {0}, szRecvBuf[HI_NVR_MAX_BUF_LEN] = {0}, *pTemp = HI_NULL;
pTemp = szSendBuf;
pTemp += sprintf(pTemp,"OPTIONS rtsp://%s:%d/%s RTSP/1.0\r\n", g_sNvrRtspInfo[u32Channel].szIpAddr, g_sNvrRtspInfo[u32Channel].u32Port, pRtspStream->szParam);
pTemp += sprintf(pTemp,"CSeq: %d\r\n", pRtspStream->u32Seq);
pTemp += sprintf(pTemp,"User-Agent: LibVLC/3.0.6 (LIVE555 Streaming Media v2016.11.28)\r\n\r\n");
s32Ret = HI_SocketSendEx(pRtspStream->s32Sockfd, (HI_U8 *)szSendBuf, strlen(szSendBuf), 2000); //向套接字中发送数据
if(HI_SUCCESS != s32Ret)
{
printf("%s:%d HI_SocketSendEx failed(%d)\n", __FUNCTION__, __LINE__, s32Ret);
return HI_NVR_RTSP_ERR_SEND;
}
printf("send: %s\n", szSendBuf); //打印发送的信息
s32Ret = HI_SocketRecv(pRtspStream->s32Sockfd, (HI_U8 *)szRecvBuf, &u32RecvLen, 2000); //接受套接字中的数据
if(HI_SUCCESS != s32Ret)
{
printf("%s:%d HI_HM_SocketRecv failed(%d)\n", __FUNCTION__, __LINE__, s32Ret);
return HI_NVR_RTSP_ERR_RECV;
}
printf("recv: %s\n", szRecvBuf); //打印接受的数据
return HI_SUCCESS;
}
这里有需要注意的地方,就是VLC是采用UDP连接的,在一些地方会有差别,比如RTP和RTCP分别采用两个端口,而TCP是采用的interleaved来区分。
注意这几点后,其余的包按照OPTIONS那样的构建方法依次构建传输后就行。
发送PLAY包后就能接收到RTP包了
RTCP
主动发送第一个RTCP_RR包:
关于RTCP_RR包的构建,参考下表:
对于TCP连接方法,需要添加一个包头来区分是RTP还是RTCP的音频/视频包.
rtcpBuf[0] = 0x24; //$
rtcpBuf[1] = 0x01; //视频RTCP_RR包
rtcpBuf[2] = (uint8_t)(((rtcpSize) & 0xFF00) >> 8);
rtcpBuf[3] = (uint8_t)((rtcpSize) & 0xFF);
其中RTP视频包为0x00,音频为0x02
RTCP视频包为0x01,音频为0x03;
HI_S32 HI_Nvr_Rtcp_RR_V(HI_U32 u32Channel, HI_Nvr_Rtsp_Stream * pRtspStream, rtcp_SR_T * Rtcp_SR_T, HI_U32 RTP_ssrc, HI_U32 frac, HI_U16 cycles, HI_S16 lost, HI_U16 max_seq, HI_U32 _last_sr_lsr, HI_U32 _last_sr_ntp_sys, HI_U32 _jitter)
{
HI_S32 s32Ret;
rtcp_RR_T Rtcp_RR_T;
HI_U32 rtcpSize = sizeof(rtcp_RR_T);
HI_CHAR* rtcpBuf = (char*)malloc(4 + rtcpSize);
HI_U32 rtcpSize_SDES = sizeof(rtcp_sdes_item_t);
HI_U32 rtcpSize_SR = rtcpSize - rtcpSize_SDES;
memset(rtcpBuf, 0, 4 + rtcpSize);
rtcpBuf[0] = 0x24; //$
rtcpBuf[1] = 0x01; //视频RTCP_RR包
rtcpBuf[2] = (uint8_t)(((rtcpSize) & 0xFF00) >> 8);
rtcpBuf[3] = (uint8_t)((rtcpSize) & 0xFF);
//设置RTCP包头
Rtcp_RR_T.common.version = 2;
Rtcp_RR_T.common.pt = RTCP_RR;
Rtcp_RR_T.common.count = 1;
Rtcp_RR_T.common.length = htons((HI_U16)((rtcpSize_SR >> 2) - 1));
// if(Rtcp_RR_T.common.length - sizeof(rtcp_RR_T))
// Rtcp_RR_T.common.p = 1;
// else
Rtcp_RR_T.common.p = 0;
//设置RTCP包参数
Rtcp_RR_T.ssrc = Rtcp_SR_T->ssrc; //RTP报文接收者SSRC
Rtcp_RR_T.Rtcp_rr_.ssrc = RTP_ssrc; //RTP报文发送者SSRC
Rtcp_RR_T.Rtcp_rr_.fraction = frac;
Rtcp_RR_T.Rtcp_rr_.lost = htonl((HI_U32)(lost)) << 8; //累计丢失的包=期望得到的包-实际获取的包数
Rtcp_RR_T.Rtcp_rr_.seq_cycles = htons(cycles);
Rtcp_RR_T.Rtcp_rr_.seq_max = max_seq; //序列号循环计数=RTP回环次数
Rtcp_RR_T.Rtcp_rr_.jitter = htonl(_jitter); //时间戳抖动值=RTP数据包时间方差
Rtcp_RR_T.Rtcp_rr_.lsr = htonl(_last_sr_lsr); //上次收到sr包时时间戳=NPT时间戳的中间32bite
HI_U32 delay = getCurMsecond() - _last_sr_ntp_sys; //此次构建RR包时系统时间 - 上次收到SR包时系统时间
HI_U32 dlsr = (delay / 1000.0f * 65536);
Rtcp_RR_T.Rtcp_rr_.dlsr = htonl(_last_sr_lsr ? dlsr : 0); //上次收到sr包到本次发送报告的延时,1/65536秒为单位
memcpy(rtcpBuf + 4, &Rtcp_RR_T, rtcpSize);
s32Ret = HI_SocketSendEx(pRtspStream->s32Sockfd, (HI_U8*)rtcpBuf, 4 + rtcpSize, 2000);
if (HI_SUCCESS != s32Ret)
{
printf("send rr error: clientFd=%d,s32Ret=%d\n", pRtspStream->s32Sockfd, s32Ret);
return HI_NVR_RTCP_ERR_SEND;
}
printf("RTCP_Header: magic=%d, channelID=%d\n", rtcpBuf[0], rtcpBuf[1]);
printf("Send RTCP_RR: version=%d, payload=%d, length=%d\n", Rtcp_RR_T.common.version, Rtcp_RR_T.common.pt, rtcpSize_SR);
printf("ssrc=%p, fraction=%d, lost=%d, seq_cycles=%d, jitter=%d, lsr=%p, dlsr=%p\n\n", RTP_ssrc, frac, lost, cycles, max_seq, _jitter, Rtcp_RR_T.Rtcp_rr_.lsr, Rtcp_RR_T.Rtcp_rr_.dlsr);
free(rtcpBuf);
rtcpBuf = NULL;
return HI_SUCCESS;
}
创建后,将该包发送,如果IP摄像头有serve端,则能用抓包接收到SR包。
后续每秒循环发送RR包即可
RTP
接受包头长度的数据,然后判断channelid来确实是什么包后根据RTP包格式和RTCP_SR
包格式来解析包。
将需要的数据存储,再写入到RR包中反馈。
篇幅有限,讲解的不是很清楚,有疑问的欢迎留言