udp接收rtp数据流
接收一帧数据后,转换为NAL单元送去解码
(这里特别说明一下,我本次用的接口是支持从连续数据流中自动分割出一个个NAL的,
但是我为了方便,接收够一帧就打包NAL送去解码)
解码成功,播放视频
H.264 简单了解
{
图像序列
H264一个图像序列的组成:SPS+PPS+SEI+一个I帧+若干个P帧。SPS、PPS、SEI、一个I帧、一个P帧都可以称为一个NALU。
H264的NALU结构:开始码+NALU头+NALU数据
264常见的帧头数据为:
00 00 00 01 67 (SPS)
00 00 00 01 68 (PPS)
00 00 00 01 65 ( IDR 帧)
00 00 00 01 61 (P帧)
00 00 00 01 41 (P帧)
……
00 00 00 01 67 (SPS)
00 00 00 01 68 (PPS)
00 00 00 01 65 ( IDR 帧)
……
开始码大小为四个字节,是一个固定值00 00 00 01(十六进制),标识一个NALU的开始。这个主要用于解码,在rtp传输中是没有的。
RTP中传输H264采用FU-A结构, 下文我已列出。
}
这里只介绍简单的RTP报文:
rtp报文格式(共有 96位 12字节):
0-------所有RTP包共有--------31
V(2) P X CC(4) M PT(7) 序列号
时戳
同步信源 SSRC
V:RTP协议的版本号,占2位,当前协议版本号为2。
P:填充标志,占1位,如果P=1,则在该报文的尾部将填充一个或多个额外的八位组,
它们不是有效载荷的一部分。
X:扩展标志,占1位,如果X=1,则在RTP报头后跟有一个扩展报头。
CC:CSRC计数器,占 4位,指示CSRC 标识符的个数。主要用于多流组合传播,比如说
5 个人发送rtp到网关边缘,边缘把5个rtp转发给另外一个边缘,然后解出原始流。
M: 标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于
音频,标记会话的开始。**标记一帧数据的结束**
PT: 有效载荷类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图
像等。
序列号:占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。
接收者通过序列号来检测报文丢失情况,重新排序报文,恢复数据。
时戳 (Timestamp):占32位,时戳反映了该RTP报文的第一个八位组的采样时刻。接收者
使用时戳来计算延迟和延迟抖动,并进行同步控制。
同步信源(SSRC)标识符:占32位,用于标识同步信源。该标识符是随机选择的,参加同
一视频会议的两个同步信源不能有相同的SSRC。
具体请参见下图:
具体的数据可以比对参考,重要的只有一点RTP12字节头
单个NAL单元包:
在有效载荷中仅包含单个NAL单位。NAL报头类型字段将等于原始NAL单元类型;
例如,在1至23的范围内,包括1至23。
聚合数据包:
用于将多个NAL单元聚合为单个RTP负载的数据包类型。该分组存在于四个版本中,即单时间聚合分组类
型A(STAP-A)、单时间聚合包类型B(STAP-B)、具有16位偏移的多时间聚合分组(MTAP)(MTAP16)
和具有24位偏移(MTAP24)的多时间聚集分组(MTAP)。分配给STAP-A、STAP-B、MTAP16和MTAP24
的NAL单元类型编号分别为24、25、26和27。
碎片单元:
用于在多个RTP包上对单个NAL单元进行分段。存在两个版本,FU-A和FU-B,分别用NAL单元类型
编号28和29标识。//主要用这个
碎片单元(FU)
{
这种有效载荷类型允许将NAL单元分割成若干RTP分组。在应用层上执行此操作,而不是依赖较低层的分段
(例如,通过IP),具有以下优点:
o有效载荷格式能够在IPv4网络上传输大于64kbytes的NAL单元,该NAL单元可能存在于预录制视频中,
特别是在高清晰度格式中(每个图片的切片数量有限制,这导致每个图片的NAL单位的限制,这可能导
致大NAL单元)。
o分段机制允许分段单个图片并应用通用前向纠错,如第12.5节所述。
仅为单个NAL单元定义分段,而不为任何聚合分组定义分段。NAL单元的片段由该NAL单元中连续八位字节的
整数组成。NAL单元的每个八位字节必须恰好是该NAL单元一个片段的一部分。
同一NAL单元的片段必须以RTP序列号递增的顺序连续发送(在第一个片段和最后一个片段之间没有发送同一
RTP分组流中的其他RTP分组)。同样,NAL单元必须按照RTP序列号顺序重新组装。
当NAL单元被分段并在碎片单元(FU)内传送时,它被称为碎片NAL单元。STAP和MTAP不得分散。
FU不得嵌套;i、 例如,FU不得包含另一FU。携带FU的RTP分组的RTP时间戳被设置为分段NAL
单元的NALU时间。
FU-As:
一个八位字节的分段单元指示符、
一个八字节的分段单位报头
分段单位有效载荷组成。
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator | FU header | |
| |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
**本次RTP传输用这个**
+-+-+-+-+-+-+-+-+-+-+-+-FU-Bs只做了解本次不用+-+-+-+-+-+-+-+-+-+-+-+-+
FU-Bs:
一个八位字节的分段单元指示符、
一个八位组的分段单元报头、
解码顺序号(DON)(按网络字节顺序)
和分段单元有效载荷组成。
换句话说,FU-Bs的结构与FU-As的结构相同,除了额外的DON字段。
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator | FU header | DON |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
| |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
对于分段NAL单元的第一分段单元,必须在交错分组化模式中使用FU-B型NAL单元。不得在任何其他情况
下使用FU-B型NAL装置。
/*
换言之,在交错分组化模式中,被分段的每个NALU具有FU-Bs作为第一片段,
随后是一个或多个FU-A片段。
*/
FU indicator八位字节具有以下格式:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI|类型|
+---------------+
+---------------+
F:
1位禁止使用零位。值0 表示NAL单元类型八位字节和有效载荷不应包含位错误或其
他语法违规。值1指示NAL单元类型八位字节和有效载荷可能包含位错误或其他语法违规。
H.264 规范要求F位等于0 。当设置F位时,建议解码器在有效载荷或NAL单元类型八位
字节中可能存在位错误或任何其他语法冲突。对于F位等于1的NAL单元,最简单的解码器反应是丢弃这
样的NAL单位。
NRI:
2位nal_ ref_ idc。值00和非零值的语义与H.264 规范保持不变。换言之,值00指示NAL单元的
内容不用
于重构用于画面间预测的参考画面。这样的NAL单元可以被丢弃而不危及参考图片的完整性。大于00
的值指示需要NAL单元的解码以保持参考图片的完整性。
最高传输等级 11,一般I帧使用
NAL Unit Type(单元类型) Content of NAL unit(单元内容) NRI (binary)
----------------------------------------------------------------
1 non-IDR coded slice 10
2 Coded slice data partition A 10
3 Coded slice data partition B 01
4 Coded slice data partition C 01
nal_unit_type:
NAL_UNKNOWN = 0, // 未使用/未指定
NAL_SLICE = 1, // 不分区、非IDR片
NAL_SLICE_DPA = 2, //编码条带数据分割块A
NAL_SLICE_DPB = 3, //编码条带数据分割块B
NAL_SLICE_DPC = 4, //编码条带数据分割块C
NAL_SLICE_IDR = 5, //IDR图像的编码条带
NAL_SEI = 6, //辅助增强信息SEI
NAL_SPS = 7, //序列参数集
NAL_PPS = 8, //图像参数集
NAL_ = 9, //访问单元分隔符
NAL_ = 10, //序列结尾
NAL_ = 11, //流结尾
NAL_FILLER = 12, //填充数据
Summary of NAL unit types and their payload structures
Type Packet Type name Section
-----------------------------------------------------------
0 undefined -
1-23 NAL unit Single NAL unit packet per H.264 5.6
24 STAP-A Single-time aggregation packet 5.7.1
25 STAP-B Single-time aggregation packet 5.7.1
26 MTAP16 Multi-time aggregation packet 5.7.2
27 MTAP24 Multi-time aggregation packet 5.7.2
28 FU-A Fragmentation unit 5.8
29 FU-B Fragmentation unit 5.8
30-31 undefined -
---------------------------------------------------------
---0 10 |1 1100--0x5c //FU-A p帧
---0 10 |1 1101--0x5d //FU-B p帧
---0 11 |1 1100--0x7c //FU-A I帧
---0 11 |1 1101--0x7d //FU-B I帧
FU指示符八位字节的类型字段中等于28和29的值分别标识FU-A和FU-B。F位的使用如第5.3节所述。
NRI字段的值必须根据分段NAL单元中的NRI字段值进行设置。
FU标头的格式如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R|类型|
+---------------+
S: 1位
当设置为1时,开始位指示分段NAL单元的开始。当以下FU有效载荷不是分段NAL单元有效载荷的开
始时开始位被设置为零。
E: 1位
当设置为1时,结束位指示分段NAL单元的结束,即,有效载荷的最后一个字节也是分段NAL单位的
最后一字节。当以下FU有效载荷不是分段NAL单元的最后一个片段时,结束位被设置为零。
R: 1位
保留位必须等于0,并且必须被接收器忽略。
类型:5位
[1]表7-1 中定义的NAL单元有效载荷类型。
FU-B中DON值的选择如第5.5 节所述。
+-------------------------------------------------------------------------------------+
---0 11 |0 0111(NAL_SPS ) --0x67 //sps 序列参数集
---0 11 |0 1000(NAL_PPS ) --0x68 //sps 图像参数集
---1 0 0 |0 0101(IDR) ---0X85 //开始 一个FU-As包 i帧
---1 0 0 |0 0000(NAL unit) ---0x81 //开始 一个FU-As包 p帧
---0 0 0 |0 0101(IDR) ---0x05 //中间 单独一个FU-As包 i帧
---0 0 0 |0 0001(NAL unit) ---0x01 //中间 单独一个FU-As包 p帧
---0 1 0 |0 0101(IDR) ---0x45 //结束 单独一个FU-As包i帧
---0 1 0 |0 0001(NAL unit) ---0x41 //结束 单独一个FU-As包p帧
+-------------------------------------------------------------------------------------+
除了RTP12字节头,这次 又多了两个字节
具体看下图:
IDR
P
这两张图是开头数据包,如果是中间包、帧结尾,第二个字节数值不一样,上文我已列出。
而且上文说rtp传输没有NAL头且没有起始码需要求得NAL头
NALU头被分散填充到FU indicator和FU header里面了
bit位按照从左到右编号0-7来算,nalu头中0-2前三个bit放在FU indicator的0-2前三个bit中,后3-7五个bit放入FU header的后3-7五个中
udp接收rtp数据流 buff存放 //一般1400字节左右,具体可以抓包观察
{
0+12(RTP头)+2(FU indicator,FU header)+负载
0+12(RTP头)+1(sps)+负载
0+12(RTP头)+1(pps)+负载
buff[1500]
input[6500]
input_head={0x00,0x00,0x00,0x01};
帧数组 input //每次接收的报文字节数是远远小于 一帧字节数的 i帧快20万字节 p帧几万字节
判断是否为视频数据
{
判断是否为sps或者pps 不是他俩 要直接丢弃此序列
{
要考虑建立连接时,可能接收UDP包是pps,直接丢弃当前一个序列
清空input 缓冲区
为sps、pps添加帧头
可以采用 00 00 00 01 +sps 丢进解码器
也可 00 00 00 01 +sps +00 00 00 01 +pps 丢进解码器
也可 00 00 00 01 +sps +00 00 00 01 +pps + 00 00 00 01+NAL_Header+IDR 丢进解码器
我采用第三种,省事。理论不可以但实测可以
}
判断是否为帧头
{
判断是否为断帧//可能没有完整
IDR不完整 //如果接收异常要丢弃 重新接收序列
p帧不完整 // 也可以重新接收序列 也可以接受下一P帧
00 00 00 01 +p/00 00 00 01 +IDR 拷贝input
}
每一帧的中间包 既不是开头也不是结尾
直接拷贝到input
判断是否为帧结束
{
收到完整一帧数据 发送到帧结构体数组中
清空缓冲区
}
}
---------考虑使用双线程---------------
/*
一帧接收完成
解码一帧压缩数据
解码时间不占用接收视频数据时间
接收视频数据可以用链表存储 (编写链表的(接收视频)增和(解一帧视频)删函数)
(需要单独一个结构体存储结构体首尾指针以及需要到的ffmpeg变量的结构体变量和SDL变量的结构体变量)
*/
--------------------------------------
1、创建套接字
2、定义ffmpeg结构体变量和SDL结构体变量
3、定义链表数据结构信息表变量
4、创建解码线程和视频刷新线程(SDL线程实现)
5、接受视频的函数主线程