ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载H264视频流
前言:
RTSP,RTCP,RTP一般是一起使用,在FFmpeg和live555这些库中,它们为了更好的适用性,所以实现起来非常复杂,直接查看FFmpeg和Live555源代码来熟悉这些协议非常吃力,这里将它们独立出来实现,以便更好的理解协议。本文主要介绍RTSP,RTCP,RTP加载H264数据流。
说明:
(1)大华IPC摄像头作为服务端
(2)在ubuntu16.04中编译实现测试程序
(3)服务端IP: 192.168.0.120
(4)客户端IP: 192.168.0.128
协议介绍:
用一句简单的话总结:RTSP发起/终结流媒体、RTP传输流媒体数据 、RTCP对RTP进行控制,同步。RTSP属于四层网络当中的应用层,RTP,RTCP属于传输层,如下图所示意。
RTSP在博客《ONVIF网络摄像头(IPC)客户端开发—最简RTSP客户端实现》中已经介绍,这里不再重复,这里主要介绍RTCP和RTP。
RTCP协议:
RTCP控制协议需要与RTP数据协议一起配合使用,当应用程序启动一个RTP会话时将同时占用两个端口,分别供RTP和RTCP使用,其中RTCP端口一定要是基数,RTP端口一定要是偶数,且是两个相邻的端口。RTP本身并不能为按序传输数据包提供可靠的保证,也不提供流量控制和拥塞控制,这些都由RTCP来负责完成。通常RTCP会采用与RTP相同的分发机制,向会话中的所有成员周期性地发送控制信息,应用程序通过接收这些数据,从中获取会话参与者的相关资料,以及网络状况、分组丢失概率等反馈信息,从而能够对服务质量进行控制或者对网络状况进行诊断。
RTCP协议的功能是通过不同的RTCP数据报来实现的,主要有如下几种类型:
- SR:发送端报告,所谓发送端是指发出RTP数据报的应用程序或者终端,发送端同时也可以是接收端。
- RR:接收端报告,所谓接收端是指仅接收但不发送RTP数据报的应用程序或者终端。
- SDES:源描述,主要功能是作为会话成员有关标识信息的载体,如用户名、邮件地址、电话号码等,此外还具有向会话成员传达会话控制信息的功能。
- BYE:通知离开,主要功能是指示某一个或者几个源不再有效,即通知会话中的其他成员自己将退出会话。
- APP:由应用程序自己定义,解决了RTCP的扩展性问题,并且为协议的实现者提供了很大的灵活性。
如果只是测试加载H264数据流,这里建立RTCP连接后不发送RTCP数据包也是可以的,那就是客户端和服务端都采用默认的发送和接收方式收发数据。下面的测试程序实现了RTCP协议,但是没有发送RTCP数据包。
RTP协议:
在加载H264数据流的时候,比较麻烦的处理环节就是RTP网络数据包的解析,这个需要完全按照协议来解析,否则会出现花屏现象或是显示不出来的情况。RTP网络数据包结构图如下:
这里对上图中的名词做个解析:
H264图像结构:
- NAL Network Abstraction Layer
- NALU Network Abstraction Layer Unit
- VCL Video Coding Layer
- VCLU Video Coding Layer Unit
H264的图像数据是在NAL里面,并且上图中00 00 00 01 6X 这些数据是解包的时候再添加上去的,在RTP网络包中并不会传输这些信息,但是正常的h264图像数据中是需要这些标签来识别不同的图片帧的。
RTP分包模式:
- SNP Single NALU Packet
- AP Aggregation Packet
- FU Fragmentation Units
上面说过RTP网络传输包一包大小为1500字节,那么对于大于1500字节的视频帧,比如I帧,那就需要分开很多包来传输,所采用的就是FU分片模式。如果数据包小于1500字节,比如SPS,PPS,SEI数据包,直接一个RTP包就可以发送完,那使用的就是SNP单包模式。另外组合包AP表示一个包里有多种视频帧类型。在这里我们用到的是单包SNP和分片包FU两种模式。
RTP Pack:
RTP协议从网络中接收或是发送的一个数据包,一般最大1500字节。实际解析的时候也是以这样的一个数据包为单位来解析。
RTP Header:
RTP网络数据包的包头,长度为12字节具体格式如下:
/***************************************************************
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | sequence number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| synchronization source (SSRC) identifier |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| contributing source (CSRC) identifiers |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*****************************************************************/
/**RTP 头结构**/
typedef struct
{
/** byte 0 **/
unsigned char bit4CsrcLen:4; /** expect 0 **/
unsigned char bit1Extension:1; /** expect 1, see RTP_OP below **/
unsigned char bit1Padding:1; /** expect 0 **/
unsigned char bit1Version:2; /** expect 2 **/
/** byte 1 **/
unsigned char bit7PayLoadType:7; /** RTP_PAYLOAD_RTSP **/
unsigned char bit1Marker:1; /** expect 1 **/
/** bytes 2,3 **/
unsigned int u32SeqNum; /** RTP sequence number **/
/** bytes 4-7 **/
unsigned int u32TimeStamp; /** RTP sequence number **/
/** bytes 8-11 **/
unsigned int u32Ssrc; /**stream number is used here **/
}RTP_HEADER_S;
RTP payload:
RTP加载的实际数据,可以是视频,也可以是音频,也可以是其他类型,这里加载的是h264视频数据,对应下面的96。数据类型如下:
/***********************************************************
PT encoding media type clock rate
name (Hz)
_____________________________________________
24 unassigned V
25 CelB V 90,000
26 JPEG V 90,000
27 unassigned V
28 nv V 90,000
29 unassigned V
30 unassigned V
31 H261 V 90,000
32 MPV V 90,000
33 MP2T AV 90,000
34 H263 V 90,000
35-71 unassigned ?
72-76 reserved N/A N/A
77-95 unassigned ?
96-127 dynamic ?
dyn H263-1998 V 90,000
Table 5: Payload types (PT) for video and combined
encodings
***********************************************************/
NALU Header:
网络抽象层单元头结构体
/*****************
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
*****************/
typedef struct
{
unsigned char bit5TYPE:5;
unsigned char bit2NRI:2;
unsigned char bit1F:1;
}RTP_NALU_HEADER_S;
FU Indicator:
分片包指示符
/****************
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
*****************/
typedef struct
{
unsigned char Bit5TYPE:5;
unsigned char BitNRI:2;
unsigned char BitF:1;
}RTP_FU_INDICATOR_S;
FU Header:
分片包头结构
/******************
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
*******************/
typedef struct
{
unsigned char Bit5TYPE:5;
unsigned char Bit1R:1;
unsigned char Bit1E:1;
unsigned char Bit1S:1;
}RTP_FU_HEADER_S;
设计思路:
- 通过ONVIF协议获取IPC网络摄像头RTSP的URL地址,为了简化测试程序,不在这里介绍,本文中使用固定地址:rtsp://192.168.0.120:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
- 通过RTSP协议发起数据流,同时获取服务端的RTCP和RTP的网络端口号,数据类型等信息。
- 建立RTCP,RTP连接,RTCP是TCP连接,RTP在这里走的是UDP连接。
- 客户端接收RTP网络数据包
- 对RTP客户端接收到的网络数据包进行解包
- 将解包之后的数据写入文件,对于H264视频帧的第一包数据,需要添加上H264帧头标签00 00 00 01 6X信息。
- 为方便其他地方使用,可以将视频数据按帧添加进队列。
问题分析:
对于网络视频流,客户端接收到视频数据,有可能出现显示不出来或是花屏的现象,问题一般定位方法有:
- 查看H264 帧标签没有添加或是添加错误。
- 查看接收到的RTP网络数据包,看网络包序号是否连续,是否有丢包,统计丢包率有多高。
- 查看RTP网络数据包的时间戳,看时间戳是否正常增加。
- 查看RTP连接的系统网络缓存有多大,看是否有数据因为缓存满了而导致数据丢失。
- 查看RTP数据流中是否同时传输了多个流,比如同时传输音频流和视频流。
代码实现:
完整代码结构如下:
在 liwen01
公众号中回复 网络编程
获取工程代码,本章代码工程名为:RtspRtcpRtpLoad_h264.tar.gz