H.264视频码流解析

NAL协议格式

NAL全称Network Abstract Layer, 即网络抽象层.
https://www.itu.int/rec/T-REC-H.264-201906-I/en

SPS vs. PPS

H.264 comes in a variety of stream formats. One variation is called “Annex B”.

(AUD)(SPS)(PPS)(I-Slice)(PPS)(P-Slice)(PPS)(P-Slice) … (AUD)(SPS)(PPS)(I-Slice).

AUD

总的来说H264的码流的打包方式有两种:

  1. annex-b byte stream format 的格式,这个是绝大部分编码器的默认输出格式,就是每个帧的开头的3~4个字节是H264的start_code,0x00000001或者0x000001。
  2. 原始的NAL打包格式,就是开始的若干字节(1,2,4字节)是NAL的长度,而不是start_code,此时必须借助某个全局的数据来获得编 码器的profile,level,PPS,SPS等信息才可以解码。
    紧随AUD,一般是SPS/PPS/SEI/IDR的组合或者简单就是一个SLICE,也就是一个帧的开始。

SPS(Sequence Parameter Set)

所谓的编码视频序列即原始视频的一帧一帧的像素数据经过编码之后的结构组成的序列。而每一帧的编码后数据所依赖的参数保存于图像参数集中。一般情况SPS和PPS的NAL Unit通常位于整个码流的起始位置。但在某些特殊情况下,在码流中间也可能出现这两种结构,主要原因可能为:

解码器需要在码流中间开始解码;
编码器在编码的过程中改变了码流的参数(如图像分辨率等);
在做视频播放器时,为了让后续的解码过程可以使用SPS中包含的参数,必须对其中的数据进行解析。

PPS(Picture Parameter Set)

图像参数集

File Video:一般只有文件Header里面有SPS、PPS里面有信息
Live Video:为防止丢失SPS/PPS包或者中途播放、每个IFrame/IDR 前面都有SPS、PPS

An IDR frame, or an I-slice can not be decoded without a SPS and PPS.

IDR vs. I-Frame

Every IDR frame is an I-frame, but not vice versa; so there can be I-frames that aren’t IDR frames.

IDR 后面的帧不会参考之前的帧,I-Frame后面的帧还会参考之前的帧。

An IDR frame is a special type of I-frame in H.264. An IDR frame specifies that no frame after the IDR frame can reference any frame before it. This makes seeking the H.264 file easier and more responsive to the player.

NAL、Frame、SPS、PPS、SLICE关系

  1. NAL可以是:一帧视频数据(IDR,I-SLICE,P-SLICE)、SPS、PPS
  2. 一个video frame可能包含多个NAL ?参考VLC
  3. 一个NAL可能会分成多个RTP数据包(STAP聚合包),多个RTP包聚合成一个NAL

STAP可以分为两种类型STAP-A,STAP-B:

1
2
3
4
5
6
7
8
9
10
11
12
Payload Packet    Single NAL    Non-Interleaved    Interleaved
Type Type Unit Mode Mode Mode
-------------------------------------------------------------
0 reserved ig ig ig
1-23 NAL unit yes yes no
24 STAP-A no yes no
25 STAP-B no no yes
26 MTAP16 no no yes
27 MTAP24 no no yes
28 FU-A no yes yes
29 FU-B no no yes
30-31 reserved ig ig ig

SPS + PPS + IDR = I frame VLC的定义是不是有误?IDR就可以认为是I-Frame

(15+4)+(4+4)+(46960+4)= 46991

image-20210204090241733

P-SLICE = p frame

1562+4=1566

image-20210204090453070

IDR = I frame

61779 + 4 = 61783

image-20210204091608639

实验

linux send h264 rtp stream:

1
2
3
4
# 录屏
gst-launch-1.0 -v ximagesrc ! video/x-raw,framerate=20/1 ! videoscale ! videoconvert ! x264enc tune=zerolatency bitrate=500 speed-preset=superfast ! rtph264pay ! udpsink host=127.0.0.1 port=5000
# h264文件
gst-launch-1.0 -v filesrc location=/home/xuleilx/mywork/ffmpeg/test.h264 ! h264parse ! rtph264pay ! udpsink host=127.0.0.1 port=5000

receive h264 rtp stream:

1
gst-launch-1.0 -v udpsrc port=5000 caps = "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H264, payload=(int)96" ! rtph264depay ! decodebin ! videoconvert ! autovideosink

Wireshark设置:

  1. 右击Decode As …,Current设置成RTP
  2. Edit -> Preferences…,Protocols->h264,DynamicRTP Type :96

image-20210204120556213

RTP承载H264数据需要将大的NAL帧分包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.如何通过RTP承载H264数据

参考博客:

https://www.jianshu.com/p/efc5ef2113da
https://www.jianshu.com/p/5aa012b76951

参考技术文档:

https://tools.ietf.org/html/rfc6184

分包参考实现ffmpeg:

libavformat
--> rtpenc_h264_hevc.c
--> ff_rtp_send_h264_hevc()
坚持原创技术分享,您的支持将鼓励我继续创作!