NALU详解与RTSP分包发送代码分析R
1、NAL全称Network Abstract Layer, 即网络抽象层。
在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。因此我们平时的每帧数据就是一个NAL单元(SPS与PPS除外)。在实际的H264数据帧中,往往帧前面带有00 00 00 01 或 00 00 01分隔符,分隔符后面紧跟着的第一个字节就是NAL,对于NAL的解析说明,后面有详细解释,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧……
如下图为一个h264的文件的二进制代码显示:
SPS为12字节(00 00 00 01不算在有效位内)
SPS(序列参数集):SPS对如标识符、帧数以及参考帧数目、解码图像尺寸和帧场模式等解码参数进行标识记录。
SEI(补充增强信息):这部分参数可作为H264的比特流数据而被传输,每一个SEI信息被封装成一个NAL单元。SEI对于解码器来说可能是有用的,但是对于基本的解码过程来说,并不是必须的。
如下图表示:
pps为5字节(00 00 00 01不算在有效位内)
PPS(图像参数集):PPS对如熵编码类型、有效参考图像的数目和初始化等解码参数进行标志记录。
标注一下:SPS、PPS内容是编码器给。
如下图表示:
注意:H.264编码时,在每个NAL前添加起始码 0x000001,解码器在码流中检测到起始码,当前NAL结束。为了防止NAL内部出现0x000001的数据,h.264又提出'防止竞争 emulation prevention"机制,在编码完一个NAL时,如果检测出有连续两个0x00字节,就在后面插入一个0x03。当解码器在NAL内部检测到0x000003的数据,就把0x03抛弃,恢复原始数据。
接下来就是IU帧,顺序如下图所示:
有的会有SEI
SEI(补充增强信息):这部分参数可作为H264的比特流数据而被传输,每一个SEI信息被封装成一个NAL单元。SEI对于解码器来说可能是有用的,但是对于基本的解码过程来说,并不是必须的。
2、NALU帧格式详解
帧格式
H264帧由NALU头和NALU主体组成。
NALU头由一个字节组成,它的语法如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
F: 1个比特.
forbidden_zero_bit. 在 H.264 规范中规定了这一位必须为 0.
NRI: 2个比特.
nal_ref_idc. 取00~11,似乎指示这个NALU的重要性,如00的NALU解码器可以丢弃它而不影响图像的回放,0~3,取值越大,表示当前NAL越重要,需要优先受到保护。如果当前NAL是属于参考帧的片,或是序列参数集,或是图像参数集这些重要的单位时,本句法元素必需大于0。
Type: 5个比特.
nal_unit_type. 这个NALU单元的类型,1~12由H.264使用,24~31由H.264以外的应用使用,简述如下:
0 没有定义
1-23 NAL单元 单个 NAL 单元包
1 不分区,非IDR图像的片
2 片分区A
3 片分区B
4 片分区C
5 IDR图像中的片
6 补充增强信息单元(SEI)
7 SPS
8 PPS
9 序列结束
10 序列结束
11 码流借宿
12 填充
13-23 保留
24 STAP-A 单一时间的组合包
25 STAP-B 单一时间的组合包
26 MTAP16 多个时间的组合包
27 MTAP24 多个时间的组合包
28 FU-A 分片的单元
29 FU-B 分片的单元
30-31 没有定义
我们还是接着看最上面图的码流对应的数据来层层分析,以00 00 00 01分割之后的下一个字节就是NALU类型,将其转为二进制数据后,解读顺序为从左往右算,如下:
(1)第1位禁止位,值为1表示语法出错
(2)第2~3位为参考级别
(3)第4~8为是nal单元类型
例如上面00000001后有27,28以及25
其中0x27的二进制码为:
0010 0111
4-8为00111,转为十进制7,参考第二幅图:7对应序列参数集SPS
其中0x28的二进制码为:
0010 1000
4-8为01000,转为十进制8,参考第二幅图:8对应图像参数集PPS
其中0x25的二进制码为:
0010 0101
4-8为00101,转为十进制5,参考第二幅图:5对应IDR图像中的片(I帧)
2 STSP分包发送代码分析
因为视频流文件比较大,当在传输过程中出现错误,那么整个视频文件就会坏掉,如果通过分包发送的方法,即使出现错误,也是只影响其中的一包数据,所以传输过程中实现反包发送还是有必要的。在其它的数据传输中实现分包发送也是比较常见的。下面对在rtsp中实现的分包发送进行分析,代码如下:
RTSP_CLIENT g_rtspClients[MAX_RTSP_CLIENT];
HI_S32 VENC_Sent(char *buffer,int buflen) //要发送的数据首地址与长度
{
HI_S32 i;
int is=0;
int nChanNum=0;
for(is=0;is<MAX_RTSP_CLIENT;is++)
{
if(g_rtspClients[is].status!=RTSP_SENDING) //检测客户算的状态是否处于发送状态,如果处于RTSP_SENDING状态则进行分包发送,否则进入下一个循环
{
continue; //这个命令,当有多个客户端的时候是有用的,但是这里只有一个客户端,并没有显示出他的作用
}
int heart = g_rtspClients[is].seqnum % 10000; //该变量没有用到,这是从live555 移植过来的,至于作用须看live55源码
char* nalu_payload;
int nAvFrmLen = 0;
int nIsIFrm = 0;
int nNaluType = 0;
char sendbuf[500*1024+32]; //发送缓冲区
nAvFrmLen = buflen; //音频或者视频的一帧数据的长度
struct sockaddr_in server; //server的ip信息填充
server.sin_family=AF_INET;
server.sin_port=htons(g_rtspClients[is].rtpport[0]); //客户端的port
server.sin_addr.s_addr=inet_addr(g_rtspClients[is].IP);
int bytes=0;
unsigned int timestamp_increse=0; // timestamp_increse=时钟频率/帧率,后面有详解
timestamp_increse=(unsigned int)(90000.0 / 25); //90000是在sdp信息中获取的
rtp_hdr =(RTP_FIXED_HEADER*)&sendbuf[0]; //填充rtp 信息的包头 大小为12字节
rtp_hdr->payload = RTP_H264;
rtp_hdr->version = 2; //版本号
rtp_hdr->marker = 0; //标记是否是一帧的开头,一帧若被分为多个包,不是最后一包的marker就被标记为0
rtp_hdr->ssrc = htonl(10); //信源标记,标记信息的来源,不能跟别的信源重复
if(nAvFrmLen<=nalu_sent_len) //小于一包,一包最多只发1400字节
{
rtp_hdr->marker=1; //因为一帧图像的数据是不会小于1400的,所以这个位置表示的是最后的一包数据,最后一包的数据将marker标记为1
rtp_hdr->seq_no = htons(g_rtspClients[is].seqnum++); //包的***,每一包都会加一
nalu_hdr =(NALU_HEADER*)&sendbuf[12]; //不足一包的填充的是nalu_hdr
nalu_hdr->F=0;
nalu_hdr->NRI= nIsIFrm;
nalu_hdr->TYPE= nNaluType;
nalu_payload=&sendbuf[13];
memcpy(nalu_payload,buffer,nAvFrmLen);
g_rtspClients[is].tsvid = g_rtspClients[is].tsvid+timestamp_increse;
rtp_hdr->timestamp=htonl(g_rtspClients[is].tsvid);
bytes=nAvFrmLen+ 13 ; //发送, 发送的长度 有效长度+13(就是12字节rtp_hdr、1字节nalu_hdr)
sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
}
else if(nAvFrmLen>nalu_sent_len) //大于一包长度,则要进行分包发送
{
int k=0,l=0;
k=nAvFrmLen/nalu_sent_len; //k个整包
l=nAvFrmLen%nalu_sent_len; //整包之后剩余的长度
int t=0;
g_rtspClients[is].tsvid = g_rtspClients[is].tsvid+timestamp_increse; //每一帧里面的timestamp 的值是一样的
rtp_hdr->timestamp=htonl(g_rtspClients[is].tsvid); //转成网络序的
while(t<=k)
{
rtp_hdr->seq_no = htons(g_rtspClients[is].seqnum++);
/***由于整包中的第一包、最后一包、以及中间的包的填充信息是不一样的,所以要分为三种方式发送****/
if(t==0) //整包里面的第一包,h264包在传输的时候,如果包太大,会被分成多个片。NALU头会被如下的2个自己代替
{
rtp_hdr->marker=0;
fu_ind =(FU_INDICATOR*)&sendbuf[12]; //sendbuf前12字节被rtp_hdr占用了,所以从第12字节开始
fu_ind->F= 0;
fu_ind->NRI= nIsIFrm; //
fu_ind->TYPE=28;
fu_hdr =(FU_HEADER*)&sendbuf[13];
fu_hdr->E=0; //分片的结尾
fu_hdr->R=0; //保留
fu_hdr->S=1; //分片的开始
fu_hdr->TYPE=nNaluType; //NaluType
nalu_payload=&sendbuf[14]; //有效数据填充
memcpy(nalu_payload,buffer,nalu_sent_len);
bytes=nalu_sent_len+14;
sendto( udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server)); //发送, 发送的长度 有效长度+14(就是12字节rtp_hdr、1字节fu_ind、1字节fu_hdr)
t++;
}
else if(k==t) //整帧最后一个整包
{
rtp_hdr->marker=1;
fu_ind =(FU_INDICATOR*)&sendbuf[12];
fu_ind->F= 0 ;
fu_ind->NRI= nIsIFrm ;
fu_ind->TYPE=28;
fu_hdr =(FU_HEADER*)&sendbuf[13];
fu_hdr->R=0;
fu_hdr->S=0;
fu_hdr->TYPE= nNaluType;
fu_hdr->E=1;
nalu_payload=&sendbuf[14];
memcpy(nalu_payload,buffer+t*nalu_sent_len,l);
bytes=l+14;
sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
t++;
}
else if(t<k && t!=0)//除了第一包与最后一包的 中间里面的包
{
rtp_hdr->marker=0;
fu_ind =(FU_INDICATOR*)&sendbuf[12];
fu_ind->F=0;
fu_ind->NRI=nIsIFrm;
fu_ind->TYPE=28;
fu_hdr =(FU_HEADER*)&sendbuf[13];
//fu_hdr->E=0;
fu_hdr->R=0;
fu_hdr->S=0;
fu_hdr->E=0;
fu_hdr->TYPE=nNaluType;
nalu_payload=&sendbuf[14];
memcpy(nalu_payload,buffer+t*nalu_sent_len,nalu_sent_len);
bytes=nalu_sent_len+14;
sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
t++;
}
/****************************************************************************************/
}
}
}
//------------------------------------------------------------
}
涉及到的结构体
typedef struct
{
int index;
int socket;
int reqchn;
int seqnum;
int seqnum2;
unsigned int tsvid;
unsigned int tsaud;
int status;
int sessionid;
int rtpport[2];
int rtcpport;
char IP[20];
char urlPre[PARAM_STRING_MAX];
}RTSP_CLIENT;
typedef enum
{
RTSP_IDLE = 0,
RTSP_CONNECTED = 1,
RTSP_SENDING = 2,
}RTSP_STATUS;
typedef struct _RTP_FIXED_HEADER RTP_FIXED_HEADER;
struct _RTP_FIXED_HEADER
{
/**//* byte 0 */
unsigned char csrc_len:4; /**//* expect 0 */
unsigned char extension:1; /**//* expect 1, see RTP_OP below */
unsigned char padding:1; /**//* expect 0 */
unsigned char version:2; /**//* expect 2 */
/**//* byte 1 */
unsigned char payload:7; /**//* RTP_PAYLOAD_RTSP */
unsigned char marker:1; /**//* expect 1 */
/**//* bytes 2, 3 */
unsigned short seq_no;
/**//* bytes 4-7 */
unsigned long timestamp;
/**//* bytes 8-11 */
unsigned long ssrc; /**//* stream number is used here. */
} __PACKED__;
typedef struct _NALU_HEADER NALU_HEADER;
struct _NALU_HEADER
{
//byte 0
unsigned char TYPE:5;
unsigned char NRI:2;
unsigned char F:1;
}__PACKED__; /**//* 1 BYTES */
typedef struct _FU_INDICATOR FU_INDICATOR;
struct _FU_INDICATOR
{
//byte 0
unsigned char TYPE:5;
unsigned char NRI:2;
unsigned char F:1;
}__PACKED__; /**//* 1 BYTES */
typedef struct _FU_HEADER FU_HEADER;
struct _FU_HEADER
{
//byte 0
unsigned char TYPE:5;
unsigned char R:1;
unsigned char E:1;
unsigned char S:1;
} __PACKED__; /**//* 1 BYTES */
2.1 timestamp
RTP timestamp是用时钟频率(clock rate)计算而来表示时间的。
RTP timestamp表示每帧的时间,由于一个帧(如I帧)可能被分成多个RTP包,所以多个相同帧的RTP timestamp相等。(可以通过每帧最后一个RTP的marker标志区别帧,但最可靠的方法是查看相同RTP timestamp包为同一帧。)
两帧之间RTP timestamp的增量 = 时钟频率 / 帧率
其中时钟频率可从SDP中获取,如:
m=video 2834 RTP/AVP 96
a=rtpmap:96 H264/90000
其时钟频率为90000(通常视频的时钟频率),若视频帧率为25fps,则相邻帧间RTP timestamp增量值 = 90000/25 = 3600。
另外,通常音频的时钟频率一般为8000
2.2 分包
h264包在传输的时候,如果包太大,会被分成多个片。NALU头会被如下的2个自己代替。
The FU indicator(上面提到的fu_ind) octet has the following format:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
别被名字吓到这个格式就是上面提到的RTP h264负载类型,Type为FU-A
The FU header(上面提到的fu_hdr) has the following format:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
S bit为1表示分片的NAL开始,当它为1时,E不能为1
E bit为1表示结束,当它为1,S不能为1
R bit保留位
Type就是NALU头中的Type,取1-23的那个值
参考:https://www.cnblogs.com/yjg2014/p/6144977.html
参考:https://blog.csdn.net/jasonhwang/article/details/7316128
上一篇: CAN通讯总结-基于STM32
下一篇: python3操作Excel
推荐阅读