背景
8020年来了,H265已经跳出小众,日趋流行!在视频大数据时代,高压缩优势也越来越明显!
所以!搞一波RTSP+HEVC+AAC取流支持!
网上资料,h264比较齐全,对h265的说明比较零散,记录一下所有关键点!
H265原始码流处理
所有的视频帧(VPS/SPS/PPS/I/P)请去掉起始码0001,RTP包不需要起始码!
这里为什么单独拉出来呢,因为我就踩了一个坑,我大意啊,没有闪!上次搞RTSP很多年了,忘了这一茬,所以出现了很多莫名其妙的错误,后面单独开一篇!
H264与H265区别
首先HEVC在H264的图像描述上由PPS和SPS增加了VPS,这三者之间的关系如图:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZwpmL5IjM5QTNwATMxEjMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
RTSP协议H264与H265的区别
首先,我们来复习一下RTSP交互流程:
OPTION–>DESCRIBE–>SETUP–>PLAY–>PAUSE–>TEARDOWN
其中最大的区别就是在第二步DESCRIBE上,两者的SDP描述信息不同!
H264
a=rtpmap:96 H264/90000
H265
a=rtpmap:96 H265/90000
就这???
这样不能说不行,因为万能的VLC/ffplay就可以播!Robust杠杠的!
但碰到比如ijkplayer等别的播放器就over了!
还有就是,延时也比较高!
这里的主要原因其实就是SDP描述不全导致
因为没有在SDP多媒体描述文件中带上VPS/SPS/PPS参数,RTSP播放器接收解码的时候就要从流里面去遍历一遍取到所有信息,也就是延时增大的主要原因!
HEVC SDP文件
主要参考本文的核心文档《RTP Payload Format for H.265HEVC Video.txt》
7.2.1 Mapping of Payload Type Parameters to SDP
我们看里面的sdp sample:
m=video 49170 RTP/AVP 98
a=rtpmap:98 H265/90000
a=fmtp:98 profile-id=1;sprop-vps= (video parameter sets data)
详细解释请点击章节名
这里我们就能看出,少了fmtp可选描述信息里面的VPS等相关视频描述参数,也就是上个章节介绍的HEVC的主要区别!
注意:VPS、SPS、PPS需要使用base64编码格式!
以下是wireshark抓完整的一次RTSP交互过程供参考
OPTIONS rtsp://192.168.10.122:554/live0 RTSP/1.0
CSeq: 2
User-Agent: LibVLC/3.0.11 (LIVE555 Streaming Media v2016.11.28)
RTSP/1.0 200 OK
CSeq: 2
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY
DESCRIBE rtsp://192.168.10.122:554/live0 RTSP/1.0
CSeq: 3
User-Agent: LibVLC/3.0.11 (LIVE555 Streaming Media v2016.11.28)
Accept: application/sdp
RTSP/1.0 200 OK
CSeq: 3
Content-Length: 266
Content-Type: application/sdp
v=0
o=- 91607499504 1 IN IP4 192.168.10.122
t=0 0
a=control:*
m=video 0 RTP/AVP 96
a=rtpmap:96 H265/90000
a=fmtp:96 sprop-vps=QAEMAf//AWAAAAMAsAAAAwAAAwBdrAk=;sprop-sps=RAHA8vA7NA==;sprop-pps=QgEBAWAAAAMAsAAAAwAAAwBdoAKAgC0WNrkkUvTcBAQEAg==
a=control:track0
SETUP rtsp://192.168.10.122:554/live0/track0 RTSP/1.0
CSeq: 4
User-Agent: LibVLC/3.0.11 (LIVE555 Streaming Media v2016.11.28)
Transport: RTP/AVP;unicast;client_port=58908-58909
RTSP/1.0 200 OK
CSeq: 4
Transport: RTP/AVP;unicast;client_port=58908-58909;server_port=36560-36561
Session: 3680
PLAY rtsp://192.168.10.122:554/live0 RTSP/1.0
CSeq: 5
User-Agent: LibVLC/3.0.11 (LIVE555 Streaming Media v2016.11.28)
Session: 3680
Range: npt=0.000-
RTSP/1.0 200 OK
CSeq: 5
Range: npt=0.000-
Session: 3680; timeout=60
TEARDOWN rtsp://192.168.10.122:554/live0 RTSP/1.0
CSeq: 6
User-Agent: LibVLC/3.0.11 (LIVE555 Streaming Media v2016.11.28)
Session: 3680
RTSP/1.0 200 OK
CSeq: 6
Session: 3680
ffmpeg里面(libavformat/rtpdec_hevc.c)的fmtp解封装过程
static av_cold int hevc_sdp_parse_fmtp_config(AVFormatContext *s,
AVStream *stream,
PayloadContext *hevc_data,
const char *attr, const char *value)
{
/* profile-space: 0-3 */
/* profile-id: 0-31 */
if (!strcmp(attr, "profile-id")) {
hevc_data->profile_id = atoi(value);
av_log(s, AV_LOG_TRACE, "SDP: found profile-id: %d\n", hevc_data->profile_id);
}
/* tier-flag: 0-1 */
/* level-id: 0-255 */
/* interop-constraints: [base16] */
/* profile-compatibility-indicator: [base16] */
/* sprop-sub-layer-id: 0-6, defines highest possible value for TID, default: 6 */
/* recv-sub-layer-id: 0-6 */
/* max-recv-level-id: 0-255 */
/* tx-mode: MSM,SSM */
/* sprop-vps: [base64] */
/* sprop-sps: [base64] */
/* sprop-pps: [base64] */
/* sprop-sei: [base64] */
if (!strcmp(attr, "sprop-vps") || !strcmp(attr, "sprop-sps") ||
!strcmp(attr, "sprop-pps") || !strcmp(attr, "sprop-sei")) {
uint8_t **data_ptr = NULL;
int *size_ptr = NULL;
if (!strcmp(attr, "sprop-vps")) {
data_ptr = &hevc_data->vps;
size_ptr = &hevc_data->vps_size;
} else if (!strcmp(attr, "sprop-sps")) {
data_ptr = &hevc_data->sps;
size_ptr = &hevc_data->sps_size;
} else if (!strcmp(attr, "sprop-pps")) {
data_ptr = &hevc_data->pps;
size_ptr = &hevc_data->pps_size;
} else if (!strcmp(attr, "sprop-sei")) {
data_ptr = &hevc_data->sei;
size_ptr = &hevc_data->sei_size;
} else
av_assert0(0);
ff_h264_parse_sprop_parameter_sets(s, data_ptr,
size_ptr, value);
}
/* max-lsr, max-lps, max-cpb, max-dpb, max-br, max-tr, max-tc */
/* max-fps */
/* sprop-max-don-diff: 0-32767
When the RTP stream depends on one or more other RTP
streams (in this case tx-mode MUST be equal to "MSM" and
MSM is in use), this parameter MUST be present and the
value MUST be greater than 0.
*/
if (!strcmp(attr, "sprop-max-don-diff")) {
if (atoi(value) > 0)
hevc_data->using_donl_field = 1;
av_log(s, AV_LOG_TRACE, "Found sprop-max-don-diff in SDP, DON field usage is: %d\n",
hevc_data->using_donl_field);
}
/* sprop-depack-buf-nalus: 0-32767 */
if (!strcmp(attr, "sprop-depack-buf-nalus")) {
if (atoi(value) > 0)
hevc_data->using_donl_field = 1;
av_log(s, AV_LOG_TRACE, "Found sprop-depack-buf-nalus in SDP, DON field usage is: %d\n",
hevc_data->using_donl_field);
}
/* sprop-depack-buf-bytes: 0-4294967295 */
/* depack-buf-cap */
/* sprop-segmentation-id: 0-3 */
/* sprop-spatial-segmentation-idc: [base16] */
/* dec-parallel-ca: */
/* include-dph */
return 0;
}
HEVC RTP封包
这里主要介绍H265的RTP包怎么封。
先复习下基础知识
HEVC Nal Unit Header
-
F: 1位
禁止的零位。在[HEVC]中要求为零。
-
Type:6位
此字段将NAL单元类型
-
LayerID、TID
LayerID表示NAL所在的Access unit所属的层,该字段是为了HEVC的继续扩展设置。TID确定了NAL所在的unit的时域上的层次
RTP封包
为什么需要知道NAL头结构体呢,因为RTP的封包格式是将所有的Nal包分成PAYLOAD_SIZE大小的包;PAYLOAD_SIZE=1420 //1460 1500-20-12-8,这里会有两种情况:
-
Nal包小于PAYLOAD_SIZE,单包直接发
4.4.1 Single NAL Unit Packets
RTSP拉流h265(hevc)+AAC关键节点详解!史上最全RTSP+hevc 交互全记录!背景H265原始码流处理H264与H265区别RTSP协议H264与H265的区别HEVC RTP封包参考
这个时候码流直接拷贝就行了,PayloadHdr=Nal
参考FFmpeg里(libavformat/rtpenc_h264_hevc.c)的实现代码
/* send it as one single NAL unit? */
if (len <= rtp_ctx->max_payload_size) //小于对定的最大值时,直接发送(最大值一般小于mtu)
{
/* use the original NAL unit buffer and transmit it as RTP payload */
ff_rtp_send_data(ctx, buf, len, last_packet_of_frame);
}
-
Nal包大于PAYLOAD_SIZE,需要分片多个RTP FU payload包
4.4.3 Fragmentation Units (FUs)
RTSP拉流h265(hevc)+AAC关键节点详解!史上最全RTSP+hevc 交互全记录!背景H265原始码流处理H264与H265区别RTSP协议H264与H265的区别HEVC RTP封包参考 RTSP拉流h265(hevc)+AAC关键节点详解!史上最全RTSP+hevc 交互全记录!背景H265原始码流处理H264与H265区别RTSP协议H264与H265的区别HEVC RTP封包参考
- PayloadHdr = 49
- FU header S=Start E=END 第一片就是S=1 这样来赋值
- FU header FuType=NalType
FU Header 实现代码:
else {
uint8_t nal_type = (buf[0] >> 1) & 0x3F;
/*
* create the HEVC payload header and transmit the buffer as fragmentation units (FU)
*
* 0 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |F| Type | LayerId | TID |
* +-------------+-----------------+
*
* F = 0
* Type = 49 (fragmentation unit (FU))
* LayerId = 0
* TID = 1
*/
s->buf[0] = 49 << 1;
s->buf[1] = 1;
/*
* create the FU header
*
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |S|E| FuType |
* +---------------+
*
* S = variable
* E = variable
* FuType = NAL unit type
*/
s->buf[2] = nal_type;
/* set the S bit: mark as start fragment */
s->buf[2] |= 1 << 7;
/* pass the original NAL header */
buf += 2;
size -= 2;
flag_byte = 2;
header_size = 3;
}
while (size + header_size > s->max_payload_size) {
memcpy(&s->buf[header_size], buf, s->max_payload_size - header_size);
ff_rtp_send_data(s1, s->buf, s->max_payload_size, 0);
buf += s->max_payload_size - header_size;
size -= s->max_payload_size - header_size;
s->buf[flag_byte] &= ~(1 << 7);
}
s->buf[flag_byte] |= 1 << 6;
memcpy(&s->buf[header_size], buf, size);
ff_rtp_send_data(s1, s->buf, size + header_size, last);
}
参考
- RTP Payload Format for H.265/HEVC Video draft-ietf-payload-rtp-h265-14.txt
- rtsp播放h265(hevc)
- 【HEVC简介】High Level Syntax
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处!
更多精彩内容,欢迎访问一只海星的主页