rtp载荷H264解包过程分析,ffmpeg解码qt展示

2023-05-16

网络抽象层单元 (NALU)

   NALU头

        NALU 头由1个byte组成, 它的语法如下:
+---------------+
|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 解码器可以丢弃它而不影响图像,不过一般情况下不太关心这个属性.
  • Type:  5 个比特.nal_unit_type表示这个 NALU 单元的类型.

        NALU头中表示的type类型

Type取值包类型名称
0未定义
1-23为NALU包,且该NALU内包含一个完整的H264帧
1不分区,非IDR图像的片
2片分区A
3片分区B
4片分区C
5IDR图像中的片
6补充增强信息单元(SEI)
7SPS
8PPS
9序列结束
10序列结束
11码流借宿
12填充
13-23保留
24STAP-A Single-time aggregation packet
25STAP-B Single-time aggregation packet
26MTAP16 Multi-time aggregation packet
27MTAP24 Multi-time aggregation packet
28FU-A Fragmentation unit
29FU-B Fragmentation unit
30-31undefined

        上述type中,单个mtu中包含一个独立完整的h264帧和多个mtu组成一个单独完整的h264帧的情形比较容易见到。以下图为例,就是我们从三光吊舱拿到的payload:


        因为我使用了wireshark的extract_h264插件,所以这里显示的是H.264字段,如果没有使用该插件,则显示的是RTP的payload字段。同图中可以看到FU identifier是一个字节0x3C,对应的是0011 1100,对照上述的NALU 头的解释,F位为0,NRI为01,表示重要性;type为28,表示这个包为FU-A分片单元,也说明这个是某个h264帧的一个分片,该帧需要多个分片构成分片方式为FU-A。

也就是说,一个使用RTP封装的h264的包的payload和h264的帧存在3种对应关系:

  • Single NAL unit packet(单NALU包): 也就是实际的NAL类型,可以理解为一个包就是一帧H264数据,这个在实际中是比较多的。
  • Aggregation packet (聚合包):一包数据中含有多个H264帧,封装在Aggregation packet中的 NAL单元大小为65535字节。
    • STAP-A 包内的帧含有相同的NALU-Time,没有DON;
    • STAP-B 包内的帧含有相同的NALU-Time,有DON
    • MTAP16 包内的帧含有不同的NALU-Time,timestamp offset = 16
    • MTAP24 包内的帧含有不同的NALU-Time,timestamp offset = 24
  • Fragmentation unit(分片包): 一帧数据被分为多个RTP包,这也是很常见的,特别是对于关键帧。现存两个版本FU-A,FU-B。实际应用就是要加上个H264 STREAM 的头h264_stream_head = 0x00,0x00,0x00,0x01 4字节,送去解码即可。

分包规则

        这里讲解上述三种情况下,h264的数据和payload的关系;

单个NAL单元包(type值为1-23)

        对于 NALU 的长度小于MTU 大小的包, 一般采用单个NAL 单元模式。

        一个原始的 H.264 NALU (也就是还原后的应该有的样子)单元常由 [Start Code] [NALU Header] [NALU Payload]三部分组成, 其中 Start Code 用于标示这是一个 NALU 单元的开始, 必须是 "00 00 00 01" 或 "00 00 01", NALU 头(上述的0x3C)仅一个字节, 其后都是 NALU 单元内容。

        对于这样的包,在rtp打包时只是去除了 "00 00 01" 或 "00 00 00 01" 的开始码(Start Code), 把其他数据(包括NALU header和NALU payload)封包的 RTP 包即可。

        一个封装单个NAL单元包到RTP的NAL单元流的RTP序号必须符合NAL单元的解码顺序。单个NAL单元包的结构显示如图。(NAL单元的第一字节和RTP荷载头第一个字节重合)
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| type | |
+-+-+-+-+-+-+-+-+ |
| |
| Bytes 2..n of a Single NAL unit |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

        一个包就是一帧数据。h264_stream_head + NAL_unit_type... 就可以直接送去解码了(也就是对于这样的包,只需要在前面加上00 00 00 01四个字节即可)。

聚合包(24-27)

  • 单时间聚合包(24-25)

        STAP应该用于当组合在一起的NAL单元共享相同的NALU时间戳。

        (1)STAP-A(24)荷载不包括DON,至少包含一个单时刻组合单元.

        (2)STAP-B(25)荷载包含一个16位的无符号解码顺序号(DON) (网络字节序)紧跟至少一个单时刻组合单元.

        (3)DON域指定STAP-B传输顺序中第一个NAL单元的DON值. 对每个后续出现在STAP-B中的NAL单元,它的DON值等于(STAP-B中前一个NAL的DON值+1)%65535, %是取模运算。

        单时刻组合单元有一个16位无符号大小信息(网络字节序)DON,它指示后续NAL单元的大小(以字节为单位)(不包括这两个字节,但包括NAL单元类型字节),后面紧跟NAL单元本身,包括它的NAL单元类型字节。单时刻聚合单元在RTP荷载中是字节对齐的,但是可以不是32位字边界对齐。

        STAP-A:一个RTP包包含一个STAP-A。STAP包含两个单时刻组合单元:
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Data |
: :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 Size | NALU 2 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 Data |
: :
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

        STAP-B:一个RTP包包含一个STAP-B. STAP包含两个单时刻组合单元:

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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|STAP-B NAL HDR | DON | NALU 1 Size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Size | NALU 1 HDR | NALU 1 Data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
: :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 Size | NALU 2 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 Data |
: :
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

        看这个结构应该很清楚了,先是16位的长度,就可以得到一帧,h264_stream_head + NALU 1 HDR...送去解码。再算下一帧。需要注意的这个NALU Size 是不包括他本身这2个字节。STAP-B还要考虑DON。

  • 多时间聚合包(26-27)

        多时刻时间包的NAL单元荷载有16位的无符号解码顺序号基址(DONB) (网络字节序)以及一个或多个多时刻聚合单元,DONB 必须包含MTAP中NAL单元的第一个NAL的DON的值。

        NAL解码顺序中的第一个NAL单元不必要是封装在MTAP中的第一个NAL单元

        两个多时刻组合单元都有16位的无符号大小信息用于后续NAL单元(网络字节序),一个8位无符号解码序号差值(DOND), 和n位 (网络字节序) 时戳位移(TS 位移)用于本NAL单元,n可以是16/24. 不同MTAP类型的选择是应用相关的时戳位移越大, MTAP的灵活性越大, 但是负担也越大。

        MTAP16/MTAP24多时刻组合单元的结构如图示。

MTAP16
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
: NAL unit size | DOND | TS offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| TS offset | |
+-+-+-+-+-+-+-+-+ NAL unit |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

MTAP24:
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
: NALU unit size | DOND | TS offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| TS offset | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| NAL unit |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

         一个包中的组合单元的开始/结束不要求位于32位的边界。跟随NAL单元的DON 等于(DONB + DOND) % 65536, %代表取摸操作. 本文没有指定MTAP内的NAL单元如何排序,但大多数情况,应该使用NAL单元解码顺序。

        时戳位移域必须设置成等于以下公式的值:如果NALU-time大于等于包的RTP时戳,则时戳位移等于(NALU-time - 包的RTP时戳).如果NALU-time小于包的RTP时戳,则时戳位移等于 NALU-time + (2^32 - 包的RTP时戳)。

(1)一个RTP包包含一个多时刻MTAP16类型的组合包,包括两个多时刻组合单元

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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|MTAP16 NAL HDR | decoding order number base | NALU 1 Size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Size | NALU 1 DOND | NALU 1 TS offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 HDR | NALU 1 DATA |
+-+-+-+-+-+-+-+-+ +
: :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 SIZE | NALU 2 DOND |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 TS offset | NALU 2 HDR | NALU 2 DATA |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
: :
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

(2)一个RTP包包含一个多时刻MTAP24类型的组合包,包括两个多时刻组合单元

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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|MTAP24 NAL HDR | decoding order number base | NALU 1 Size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Size | NALU 1 DOND | NALU 1 TS offs |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|NALU 1 TS offs | NALU 1 HDR | NALU 1 DATA |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
: :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 SIZE | NALU 2 DOND |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 TS offset | NALU 2 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 DATA |
: :
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

        看这个结构应该很清楚了,先是16位的DONB,然后是16位的长度,8位的DOND,根据DONB计算出DON,去掉时间戳(16-24bits),就可以得到一帧,h264_stream_head + NALU 1 HDR...。得到该RTP包中所有的NAL单元后,根据DON确定解码顺序。需要注意的这个NALU Size 是不包括他本身这2个字节。

分片单元 (FUs)(28-29)

        当 NALU 的长度超过 MTU 时, 就必须对 NALU 单元进行分片封包,NAL单元的一个分片由整数个连续NAL单元字节组成. 每个NAL单元字节必须正好是该NAL单元一个分片的一部分。相同NAL单元的分片必须使用递增的RTP序号连续顺序发送(第一和最后分片之间没有其他的RTP包)。相似, NAL单元必须按照RTP顺序号的顺序装配。

        当一个NAL单元被分片运送在分片单元(FUs)中时,被引用为分片NAL单元。STAPs,MTAP不可以被分片。 FUs不可以嵌套,即,一个FU 不可以包含另一个FU. 运送FU的RTP时戳被设置成分片NAL单元的NALU时刻.

        FU-A的RTP荷载格式(上图就是这种格式)

        FU-A由1字节的分片单元指示(上图的0x3C),1字节的分片单元头(上图的0x81),和分片单元荷载组成。

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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

        FU-B的RTP荷载格式

        FU-B由1字节的分片单元指示,1字节的分片单元头,和解码顺序号(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单元的第一个分片如果用于交错打包方式,则必须使用NAL单元类型FU-B。NAL单元类型FU-B MUST不可以用于其他情况。换句话, 在交错打包方式,每个被分片的NALU,FU-B作为第一个分片,后面跟随的是一个或多个FU-A分片。

        FU指示字节(FU identifier)有以下格式:

+--------------------+
|0 |1|2 |3|4| 5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+--------------------+

        FU指示字节的类型域(type)取值为28或29,分别表示FU-A和FU-B。NRI域的值必须根据分片NAL单元的NRI域的值设置。

        FU头(FU Header)的格式如下:

+--------------------+
|0 |1|2 |3|4 |5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+--------------------+

  • S:1 表示是一帧的开始包(上图图中的0x81的第一位为1,表示一个帧的开始)
  • E:1 表示是一帧的结束包,和RTP marker位一致
  • R:0 必须

        这里要注意一下,组包时,NAL unit 的头的1个字节必须自己拼装FU Indicator前3字节+ FU Header后5字节。nal_unit_type= (fu_indicator & 0xe0) | (fu_header & 0x1f),等帧收齐了,加上H264_streaming_head + nal_unit_type....送去解码。以上图为例,fu_identifier为0x3C,即0011 1100, 取其前3位为001, fu_header为0x81,即1000 0001,取其后5位为00001,拼到一起,就是0x21。事实上,我们得到的h264帧还真是这样的。如下图所示:

         对比第一张图,可以看到,我们得到的h264的帧是前面加了00 00 00 01四个字节,把fu_identifier和fu_header换成了0x21,就是上面计算得到的。

        下面验证一下构成一个完整h264帧的中间包和尾包的fu_header和fu_identifier是什么样子的。

         上图为本文第一张图的后续,可以看到FU identifier为0x3C和第一个图一样,但FU Header为0x01,说明该包不是一个帧的开头,而是中间帧,也不是帧的结束。

        上图为上述h264帧对应的多个包的最后一个,可以看到FU identifier为0x3C和第一个图一样,但FU Header为0x41,说明该包是帧的结束。

数据分析

        首先看一下序列参数集sps、图像参数集pps的处理方式:

{0x80, 0x60, 0x00, 0x01, 0x05, 0xac, 0x32, 0xf8, 0x11, 0xe1, 0xa6, 0x6a, 0x67, 0x42, 0x00, 0x29,0x8d, 0x8d, 0x40, 0x3c, 0x01, 0x13, 0xf2, 0xcd, 0x41, 0x40, 0x80, 0x81, 0xe1, 0x10, 0x8d, 0xc0}

        上图蓝色线圈中的区域的前12个字节为RTP头,这部分在解析H264时可以暂时不用处理,先忽略掉。后面第一个字节为0x67,PayLoadType=0x67 & 0x1F,计算后通过表1可以看到为sps,在处理这部分时只需要去掉RTP头部,在前面加上00 00 00 01四个字节后直接写入缓存。

{0x80, 0x60, 0x00, 0x02, 0x05, 0xac, 0x32, 0xf8, 0x11, 0xe1, 0xa6, 0x6a, 0x68, 0xca, 0x43, 0xc8}

        同样,根据sps的分析过程,该帧为pps,处理方式与sps一样即可。   

{0x80, 0x60, 0x00, 0x03, 0x05, 0xac, 0x32, 0xf8, 0x11, 0xe1, 0xa6, 0x6a, 0x7c, 0x85, 0x88, 0x80,0x00, 0x40, 0x00, 0x00, 0xa7, 0xff, 0xff, 0xc5, 0xc5, 0x00, 0x02, 0xbf, 0xc0, 0x03, 0xdb, 0x8a, 截取了一部分数据。

        上面红色背景部分12字节为RTP头,后面一个字节0x7C为分片单元指示,0x85为分片单元头。

        0x7C & 0x1F = 28(FU-A类型) ,FU Header为0x7C,对应二进制为0111 1100,前3位为011。

       FU identifier 0x85的二进制为:1000 1001,后5位为01001。

        通过分析可以看到数据包为FU-A类型,该分片是一帧的开始,而且是关键帧。在处理FU-A时,上面已经介绍了方法,再来计算一下NAL unit type= 0x7C & 0xE0 | NALType,为0x65,在组包时需要把RTP头、分片单元指示字节和分片单元头字节去掉,在前面加上0x00 0x00 0x00 0x01 0x65字节然后存入缓存即可。

{0x80, 0x60, 0x00, 0x04, 0x05, 0xac, 0x32, 0xf8, 0x11, 0xe1, 0xa6, 0x6a, 0x7c, 0x05, 0xea, 0x3f,
0x09, 0xf3, 0xda, 0x7f, 0x57, 0x7f, 0xa7, 0xf5, 0xff, 0xfb, 0xe2, 0xba, 0xdd, 0x77, 0xd2, 0xff, 截取了一部分数据。

        红色部分为RTP头,后面一个字节0x7C为分片单元指示,0x05为分片单元头。

0x7C & 0x1F = 28(FU-A类型) ,0x05的二进制为:0000 1001?

        通过分析可以看出该包为中间包,处理方式比较简单,去掉RTP头、分片单元指示和分片单元头,继续存入缓存中即可。

{0x80, 0xe0, 0x00, 0x2c, 0x05, 0xac, 0x32, 0xf8, 0x11, 0xe1, 0xa6, 0x6a, 0x7c, 0x45, 0x1c, 0x67,
0xba, 0x5a, 0x9a, 0x2e, 0x6a, 0x73, 0x98, 0xfa, 0x99, 0x8b, 0x86, 0x2f, 0xcf, 0xf1, 0x4c, 0x5b,截取了一部分数据。

        红色部分为RTP头,后面一个字节0x7C为分片单元指示,0x45为分片单元头。

0x7C & 0x1F = 28(FU-A类型) ,0x45的二进制为:0100 1001?

        通过分析可以看出该包为结束包,处理方式比较简单,去掉RTP头、分片单元指示和分片单元头,继续存入缓存中即可。

        到此一帧数据提取完毕,就可以送给ffmpeg解析了。

使用ffmpeg解码H264

        在使用ffmpeg解码时,需要循环接收你提取的H264数据,然后再进行解析即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

int pos = 0;
do
{
    uint8_t *poutBuf;
    int pout_len;
    int len = av_parser_parse2(m_pCodecParserContext, m_pCodecContext,
        &poutBuf, &pout_len, (uint8_t*)pData + pos, nLength - pos,
        AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
    pos += len;
    if (pout_len > 0)
    {
        AVPacket packet;
    av_init_packet(&packet);
    packet.data = poutBuf;
    packet.size = len ;
    packet.pts = 90000/25 * Count++;
    int ret = -1;
    ret = avcodec_send_packet(m_pCodecContext, &packet);
    if (ret < 0)
    {
        continue;
    }
    while (!ret)
    {
        ret = avcodec_receive_frame(m_pCodecContext, m_pSrcFrame);
        if (!ret)
        {
            msleep(20);
            int w = m_pCodecContext->width;
            int h = m_pCodecContext->height;
            if (m_pRGBSwsContext == NULL)
            {
                m_pRGBSwsContext = sws_getContext(w, h, m_pCodecContext->pix_fmt, w, h, AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
                av_image_alloc(m_pFrameRGB->data, m_pFrameRGB->linesize, w, h, AV_PIX_FMT_RGB32, 1);
            }
            sws_scale(m_pRGBSwsContext, (uint8_t const * const *)m_pSrcFrame->data, m_pSrcFrame->linesize, 0, h, m_pFrameRGB->data, m_pFrameRGB->linesize);
            //把这个RGB数据 用QImage加载
            QImage tmpImg((uchar *)m_pFrameRGB->data[0], m_pCodecContext->width, m_pCodecContext->height, QImage::Format_RGB32);
            QImage image = tmpImg.copy();
            //把图像复制一份 传递给界面显示
            emit signal_sendQImage(image);
            av_frame_unref(m_pSrcFrame);
        }

    }
    }
}

这是使用Qt + VS开发的,视频能够正常播放。

参考:Rtp载荷H264解包过程分析,ffmpeg解码qt展示 | 码农家园(该文错误地方较多,我在本文进行了修正)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

rtp载荷H264解包过程分析,ffmpeg解码qt展示 的相关文章

  • 消除 ffmpeg 和 image-magic 命令中的慢速因素

    这些命令的基本思想是创建一个比较 比较定义了过去的一张 jpeg 和现在的一张 jpeg 组合起来 例如它们将相互滑动并显示前后图像 e g https media evercam io v1 cameras 1lowe scnoe com
  • 如何使用ffmpeg重叠和合并多个音频文件?

    我正在尝试将多个音频文件合并到一个文件中 但我可以使用以下命令来连接 而不是连接 ffmpeg v debug i file1 wav i file2 wav i file3 wav filter complex 0 0 concat n
  • MP4 到 DASH(bash 脚本)

    我有一个网站 用户可以在其中上传视频文件 我想使用 DASH 流式传输所有内容以获得自适应比特率流式传输 因此 我编写了一个 bash 脚本 由 cron 运行 将所有 mp4 文件转换为 DASH 但它无法正常工作 出了什么问题 例如 使
  • 使用 mkfifo 和传输流,这可能吗?

    我想执行一个 bash 脚本来执行以下操作 应用程序 ffmpeg 生成实时传输流 ts 文件 我需要处理这个实时流 执行解复用等 现在我知道这必须通过 FIFO 来完成 但这是我的任务 我需要重定向 ffmpeg 的输出以写入 fifo
  • 如何在Android项目中使用libffmpeg.so?

    我正在尝试在 Android 中创建一个屏幕录制应用程序 为此 我使用 FFmpeg 我已经创建了 libffmpeg so 文件 现在我想在 Android 项目中使用相同的方法来调用它的本机函数 我怎样才能做到这一点 本教程提供了有关此
  • 如何使用android ndk r9b为Android编译FFMPEG

    我想设计一个Android应用程序 可以通过FFMPEG命令播放和编辑视频 但我不知道如何在Android上使用FFMPEG 我尝试过从Google搜索到的许多方法 但它们太旧了 无法实现 现在 FFMPEG的最新版本是2 1 1 Andr
  • 使用 ffmpeg 或 OpenCV 处理原始图像

    看完之后维基百科页面 http en wikipedia org wiki Raw image format原始图像格式 是任何图像的数字负片 为了查看或打印 相机图像传感器的输出具有 进行处理 即转换为照片渲染 场景 然后以标准光栅图形格
  • 使用 Ffmpeg 编辑视频元数据

    我想更改视频元数据 原始视频信息 ffmpeg i video mp4 Metadata major brand mp42 minor version 0 compatible brands isomavc1mp42 creation ti
  • OpenCV IP 相机应用程序崩溃 [h264 @ 0xxxxx] 访问单元中缺少图片

    我在 cpp 中有一个 opencv 应用程序 它使用 opencv 的简单结构捕获视频流并将其保存到视频文件中 它与我的网络摄像头完美配合 但是 当我运行它从 IP 摄像机捕获流时 它可能会在大约十秒后崩溃 我的编译命令是 g O3 IP
  • Python 子进程(ffmpeg)仅在我按 Ctrl-C 程序时启动?

    我正在尝试使用 Cygwin 和 Python 2 7 并行运行一些 ffmpeg 命令 这大概是我所拥有的 import subprocess processes set commands ffmpeg i input mp4 outpu
  • 在 macOS 上为 MoviePy 安装 ffmpeg 失败并出现 SSL 错误

    我正在尝试编写一个 Python 程序 在 Mac OS 10 11 16 上使用 MoviePy 将 MP4 文件转换为 GIF 我用 import moviepy editor as mp 我收到一条错误消息 说我需要打电话imagei
  • Windows 上的 ffmpeg-android ndk

    我正在尝试编译 bash 文件 带有 android ndk 的 ffmpeg 我收到如下错误 arm linux androideabi gcc 无法创建可执行文件 C 编译器测试失败 Makefile 2 config mak 没有这样
  • MediaCodec 创建输入表面

    我想使用 MediaCodec 将 Surface 编码为 H 264 使用 API 18 有一种方法可以通过调用 createInputSurface 然后在该表面上绘图来对表面中的内容进行编码 我在 createInputSurface
  • 致命错误:libavcodec/avcodec.h 没有这样的文件或目录编译终止

    我正在尝试使用 gcc 执行tutorial01 c 并且 gcc 和tutorial01 c 以及 libavcodec 和 libavformat 及其关联文件位于同一文件夹中 它给了我这个错误 致命错误 libavcodec avco
  • 如何让 Python 找到 ffprobe?

    I have ffmpeg and ffprobe安装在我的 mac macOS Sierra 上 并且我已将它们的路径添加到 PATH 中 我可以从终端运行它们 我正在尝试使用ffprobe使用以下代码获取视频文件的宽度和高度 impor
  • 使用 FFMPEG 添加覆盖并最少重新编码

    FFMPEG 对于剪切视频的一部分非常有用 而无需重新编码视频 我知道也可以使用 FFMPEG 添加叠加图像到视频的某个部分 例如从 10 秒到 20 秒 我的问题是 如果我对图像进行叠加 整个视频是否会因此而重新编码 或者只对相关的持续时
  • 将 ffmpeg 编译为独立二进制文件

    我正在尝试编译ffmpeg作为独立的二进制文件 因为我想在 AWS lambda 中使用它 我可以让事情在我正在编译的服务器上正常工作 但是如果我复制二进制文件并从另一台服务器运行它 我会得到 ffmpeg error while load
  • Xuggler 未转换 .webm 文件?

    我只是尝试使用 Xuggler 将 mov 文件转换为 webm 这应该可以工作 因为 FFMPEG 支持 webm 文件 这是我的代码 IMediaReader reader ToolFactory makeReader home use
  • 如何将AVFrame转换为glTexImage2D使用的纹理?

    如您所知 AVFrame 有 2 个属性 pFrame gt data pFrame gt linesize 当我从视频 sdcard test mp4 android平台 读取帧后 并将其转换为RGB AVFrame副 img conve
  • ffmpeg h264 问题:在 XP 上“找不到预设文件...”

    我有 XP 因为我不熟悉编译 所以我下载了 ffmpeg 的 win32 静态版本 svn r26251 我想调整 mp4 最初为 1280x720 视频的大小 以获得较小的文件大小 但质量大致相同 我的命令是 ffmpeg i ma mp

随机推荐

  • C++ 用libcurl库进行http通讯网络编程

    转自 http www cnblogs com moodlxs archive 2012 10 15 2724318 html 目录索引 xff1a 一 LibCurl基本编程框架 二 一些基本的函数 三 curl easy setopt函
  • 群晖NAS教程(一) 、利用Docker安装MySQL8并远程访问

    为了更好的浏览体验 欢迎光顾勤奋的凯尔森同学个人博客 群晖NAS教程 一 利用Docker安装MySQL8并远程访问 做为一个NAS发烧友玩家 在追求极致硬件配置的同时 也想在各个方面压榨一下自己的黑群晖 要不就对不起自己投入那么多的毛爷爷
  • 群晖NAS教程(二)、利用Docker安装Ubuntu并远程访问

    为了更好的浏览体验 欢迎光顾勤奋的凯尔森同学个人博客 群晖NAS教程 二 利用Docker安装Ubuntu并远程访问 作者 小景哥哥 一 下载镜像ubuntu upstart 一定要选这个镜像 双击运行 二 设置Ubuntu启动参数和端口号
  • 群晖NAS教程(五)、利用Docker安装Ubuntu-21.04并在Ubuntu上安装Redis进行远程访问

    为了更好的浏览体验 欢迎光顾勤奋的凯尔森同学个人博客 群晖NAS教程 五 利用Docker安装Ubuntu 21 04并在Ubuntu上安装Redis进行远程访问 由于上节我们安装的Ubuntu完全可以当做一个服务器来使用 这个可以完全替代
  • 群晖NAS教程(七)、利用Docker安装elasticsearch并进行远程访问

    为了更好的浏览体验 欢迎光顾勤奋的凯尔森同学个人博客 群晖NAS教程 七 利用Docker安装elasticsearch并进行远程访问 一 下载elasticsearch官方镜像 然后直接下载elasticsearch镜像即可 二 配置el
  • Airsim通过ros发布激光雷达数据+Lego-loam仿真测试(2)

    上篇博客只是简单跑通了流程 xff0c 存在的问题将在这篇进行修正 一 Lego loam里话题订阅 雷达点云话题为 xff1a velodyne points xff0c frame id为velodyne IMU话题为 xff1a Im
  • [控制算法]

    常用控制算法 0 博览众长 0 1 视频 1 DR CAN b站 0 2 文章 1 控制算法整理 0 3 传统 VS 现代控制算法 1 传统 传统控制算法 xff1a PID xff0c 模糊 xff0c 神经网络控制算法 2 现代 现代控
  • 七、输入/输出流--streambuffer类介绍--

    缓冲区类 类模板定义为basic streambuf xff0c 由 lt iostream gt 给出 xff1a 1 stream缓冲区 通常stream不负责实际读写操作 xff0c 而是stream buffer实现streambu
  • 七、输入/输出流--streambuffer类介绍--自定义缓冲区

    基本上没看懂 xff0c 那个大神如果可以的话 xff0c 推荐一点相关资料 xff0c 真的不太明白这个缓冲区的内部原理 3 自定义缓冲区 缓冲区有basic streambuf定义 xff0c 针对字型为char和wchar标准库提供了
  • 串口通信学习(GPS模块)2021.5.10

    GPS串口通信学习实践 2021 5 10 1 串口通信简介1 1 波特率1 2 数据位1 3 停止位1 4 奇偶校验位 2 GPS模块串口通信配置2 1 驱动安装2 2 插入GPS模块2 3 GPS模块串口通信数据简介 3 Java实现G
  • Lambda表达式的使用

    对于jdk1 8其实并不是那么熟悉 xff0c 但是要学习这一点 xff0c 对以后工作有好处 xff0c 接下来开始学习jdk1 8在Android studio的配置以及lambda表达式的使用吧 Lambda表达式 jdk1 8中新增
  • ROS实现无人机目标跟踪/物体跟随/循迹

    无人机自主物体跟随 循迹 1 物体跟踪1 1 实现思路1 2 代码示例 2 自主寻线 本实验采用ROS和OpenCV实现功能 xff0c 实验平台采用Parrot的Bebop2无人机ROS部分的学习可以参考我的专栏 xff1a ROS学习记
  • vs code中项目的基本配置--include路径、运行参数、debug配置

    1 安装C C 43 43 for Visual Studio Code 点击左边扩展栏图标 gt 搜索C C 43 43 gt 安装 gt Reload xff1a 安装完成之后 xff0c 打开你的包含c 43 43 的文件夹 xff0
  • CMakeLists.txt编写规范模板及CMake常用变量

    文章目录 基本语法规则常见CMakeLists txt中指令剖析从VS项目配置过程理解CMakeLists内容CMake中常用变量汇总常用CMakeLists文件模板 基础模板使用OpenCV库CMakeLists文件模板使用PCL库CMa
  • C++ 多线程detach()操作的坑以及传参

    detach 的作用是将子线程和主线程的关联分离 xff0c 也就是说detach 后子线程在后台独立继续运行 xff0c 主线程无法再取得子线程的控制权 xff0c 即使主线程结束 xff0c 子线程未执行也不会结束 当主线程结束时 xf
  • 条件变量中的唤醒丢失问题分析

    本文是在其他作者博文的基础上进行了部分补充 原文 xff1a https zhuanlan zhihu com p 55123862 0 前言 条件变量 xff08 condition variable xff09 和互斥锁 xff08 m
  • 无人车传感器 IMU与GPS数据融合进行定位机制

    前言 上一次的分享里 xff0c 我介绍了GPS的原理 xff08 三角定位 xff09 及特性 xff08 精度 频率 xff09 xff0c 同时也从无人车控制的角度 xff0c 讨论了为什么仅有GPS无法满足无人车的定位要求 为了能让
  • C++类对象的赋值与=运算符重载

    本文主要介绍C 43 43 中的赋值运算符重载函数 xff08 operator 61 xff09 的相关知识 1 概述 1 1 why 首先介绍为什么要对赋值运算符 61 进行重载 某些情况下 xff0c 当我们编写一个类的时候 xff0
  • 微信小程序开发入门(一)

    所有示例的完整代码 xff0c 都可以从 GitHub 的代码仓库下载 一 小程序是什么 xff1f 学习小程序之前 xff0c 先简单说一下 xff0c 它到底是什么 字面上讲 xff0c 小程序就是微信里面的应用程序 xff0c 外部代
  • rtp载荷H264解包过程分析,ffmpeg解码qt展示

    网络抽象层单元 NALU NALU头 NALU 头 由1个byte组成 它的语法如下 43 43 0 1 2 3 4 5 6 7 43 43 43 43 43 43 43 43 43 F NRI Type 43 43 F 1 个比特 for