ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载H264视频流

发布时间 2023-04-20 19:43:34作者: liwen01

前言:

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;

设计思路:

  1. 通过ONVIF协议获取IPC网络摄像头RTSP的URL地址,为了简化测试程序,不在这里介绍,本文中使用固定地址:rtsp://192.168.0.120:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
  2. 通过RTSP协议发起数据流,同时获取服务端的RTCP和RTP的网络端口号,数据类型等信息。
  3. 建立RTCP,RTP连接,RTCP是TCP连接,RTP在这里走的是UDP连接。
  4. 客户端接收RTP网络数据包
  5. 对RTP客户端接收到的网络数据包进行解包
  6. 将解包之后的数据写入文件,对于H264视频帧的第一包数据,需要添加上H264帧头标签00 00 00 01 6X信息。
  7. 为方便其他地方使用,可以将视频数据按帧添加进队列。

问题分析:

对于网络视频流,客户端接收到视频数据,有可能出现显示不出来或是花屏的现象,问题一般定位方法有:

  1. 查看H264 帧标签没有添加或是添加错误。
  2. 查看接收到的RTP网络数据包,看网络包序号是否连续,是否有丢包,统计丢包率有多高。
  3. 查看RTP网络数据包的时间戳,看时间戳是否正常增加。
  4. 查看RTP连接的系统网络缓存有多大,看是否有数据因为缓存满了而导致数据丢失。
  5. 查看RTP数据流中是否同时传输了多个流,比如同时传输音频流和视频流。

代码实现:

完整代码结构如下:

​在 liwen01 公众号中回复 网络编程 获取工程代码,本章代码工程名为:RtspRtcpRtpLoad_h264.tar.gz

---------------------------End---------------------------
长按识别二维码
关注 liwen01 公众号