RTSP/RTP/RTCP 客户端实现

这里写自定义目录标题

简介

这里实现客户端连接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包做参考。
OPTIONS

按照上图构建

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来区分。
UDP的setup包
TCP的SETUP包
注意这几点后,其余的包按照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包中反馈。

篇幅有限,讲解的不是很清楚,有疑问的欢迎留言

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值