规范
RTP over TCP,不需要额外的RTP端口和RTCP端口,直接使用原先RTSP与VLC连接的TCP socket传输数据。 因为RTSP、RTP、RTCP都使用同一个socket通道,所以我们需要对不同协议的封包进行区分。在RTP层上添加一层,叫做rtsp interleaved frame层。
struct rtp_packet {
uint8_t interleaved[4]; // interleaved层
struct rtp_header header; // 12 Bytes
uint8_t payload[0]; // 柔性数组
};
interleaved层(4字节):
第一个字节为'$',用于区分RTP/RTCP包和RTSP包;
第二个字节为channel,用于区分RTP包和RTCP包
interleaved=rtp_channel-rtcp_channel
interleaved=0-1,表示视频流;
interleaved=2-3,表示音频流;
rtp_channel一般为偶数,rtcp_channel一般为奇数。
第三字节和第四字节表示RTP层(rtp_header+rtp_payload)的长度。
场景
在建立RTSP连接之后,通过rtp over tcp接收视频数据,在下面的例子中获取到的数据流,还是存在相隔2个RTP负载的问题,但是从字节来看,RTP的负载非常小,应该对数据没有任何的影响
说明
m_sock->RecvBuffer(pszOneBytes, nRealReadLen, 1);第二个参数是真实读取到的字节数,第三个参数是指定当前读取多少个字节
代码
struct RTPHeader
{
unsigned char szHeader[4];
};
unsigned short sRTPPayLoadLen = 0;//整个RTP报文的长度,由$ 0x00|0x01 后面的两个字节决定
unsigned char cOneBytes = 0;//保存第一个字节,进行判断是否是$,从而判断是否是RTP负载包,还是RTSP指令包
char* pszOneBytes = (char*)&cOneBytes;//接收数据只能通过符号字符,因此采用强制转换
int nRealReadLen = 0;//保存了每一次读取的字节个数
int nRet = m_sock->RecvBuffer(pszOneBytes, nRealReadLen, 1);//建立rtsp链接之后,开始读取第一个字节
if (nRet <= 0)
{
exit(0);
}
while (true)
{
if ('$' == cOneBytes)
{
nRet = m_sock->RecvBuffer(pszOneBytes, nRealReadLen, 1);
if ((nRet <= 0) || (nRet != 1)) break;
if ('$' == cOneBytes) continue;//两个$$相连也是有可能的,重新循环
if (0x00 == cOneBytes || 0x01 == cOneBytes)//读取到有效的数据
{
nRet = m_sock->RecvBuffer(pszOneBytes, nRealReadLen, 1);//尝试读取高位的负载长度
if ((nRet <= 0) || (nRet != 1)) break;
if ('$' == cOneBytes) continue;
sRTPPayLoadLen = cOneBytes << 8;
if (sRTPPayLoadLen < -1 || sRTPPayLoadLen > 1500) continue;//高位有可能是0,因为有时候多个RTP只是携带格式信息,但是没有携带数据负载
nRet = m_sock->RecvBuffer(pszOneBytes, nRealReadLen, 1);//尝试读取低位的负载长度
if ((nRet <= 0) || (nRet != 1)) break;
//if ('$' == cOneBytes) continue;//有可能低位刚好就是24,所以这一点是不需要担心的
sRTPPayLoadLen = sRTPPayLoadLen + cOneBytes;
if (sRTPPayLoadLen < 0 || sRTPPayLoadLen > 1500) continue;//数据不可能超过1500,局域网传输带宽要求
char szRTPPayLoadContent[1500] = { 0 };
nRealReadLen = 0;
nRet = m_sock->RecvBuffer(szRTPPayLoadContent, nRealReadLen, sRTPPayLoadLen);
RTPHeader* pHeader = (RTPHeader*)szRTPPayLoadContent;
unsigned short sRTPSeq = (pHeader->szHeader[2] << 8) + pHeader->szHeader[3];
//std::cout << "curSeq:" << sRTPSeq << std::endl;
static int nLastSeq = sRTPSeq;
int nGap = sRTPSeq - nLastSeq;
if (nGap != 1)
{
std::cout << "rtp seq gap:" << nGap <<",lastSeq:"<<nLastSeq<<",curSeq:"<<sRTPSeq << std::endl;
}
nLastSeq = sRTPSeq;
//循环读取负载的长度报文,一次可能读取不了完整的一个RTP负载的报文
while (nRealReadLen < sRTPPayLoadLen)
{
memset(szRTPPayLoadContent, 0x00, 1500);
sRTPPayLoadLen = sRTPPayLoadLen - nRealReadLen;
nRealReadLen = 0;
nRet = m_sock->RecvBuffer(szRTPPayLoadContent, nRealReadLen, sRTPPayLoadLen);
}
}
}
nRet = m_sock->RecvBuffer(pszOneBytes, nRealReadLen, 1);
if ((nRet <= 0) || (nRet != 1)) break;
}