前言
rtsp(Real Time Streaming Protocol)协议可以实现音视频的实时传输。安防摄像机标配之一就是支持rtsp协议。本文主要从下面几个方面对rtsp协议进行介绍:
1、rtsp协议总览
2、rtsp信令交互流程
3、rtsp鉴权
4、H264的rtp打包
5、tcp如何传输rtp
6、typescript的客户端实现
rtsp协议总览
RTSP相关规范
RFC2326:主要介绍rtsp的信令的交互流程以及鉴权,还包含了TCP/UDP传输rtp包。
RFC3984:主要介绍了H264如何打包到rtp包里面
RFC4566:SDP规范的介绍
RFC3550:RTP头的介绍
rtsp信令交互
最简单的信令交互
前面四步都是信令交互,步骤5是rtp的媒体数据。
步骤一:OPTIONS
请求
OPTIONS rtsp://10.10.14.168:554/Streaming/Channels/101 RTSP/1.0
CSeq: 2
User-Agent: LibVLC/2.2.1 (LIVE555 Streaming Media v2014.07.25)
应答
RTSP/1.0 200 OK
CSeq: 2
Public: OPTIONS, DESCRIBE, PLAY, PAUSE, SETUP, TEARDOWN, SET_PARAMETER, GET_PARAMETER
Date: Sat, Aug 14 2021 14:54:59 GMT
步骤二:DESCRIBE
请求
DESCRIBE rtsp://10.10.14.168:554/Streaming/Channels/101 RTSP/1.0
CSeq: 3
User-Agent: LibVLC/2.2.1 (LIVE555 Streaming Media v2014.07.25)
Accept: application/sdp
应答
RTSP/1.0 401 Unauthorized
CSeq: 3
WWW-Authenticate: Digest realm="IP Camera(C3459)", nonce="092598ce6a93eb21c740b7cac1acd5aa", stale="FALSE"
WWW-Authenticate: Basic realm="IP Camera(C3459)"
Date: Sat, Aug 14 2021 14:54:59 GMT
重新请求,携带鉴权
DESCRIBE rtsp://10.10.14.168:554/Streaming/Channels/101 RTSP/1.0
CSeq: 4
Authorization: Digest username="admin", realm="IP Camera(C3459)", nonce="092598ce6a93eb21c740b7cac1acd5aa", uri="rtsp://10.10.14.168:554/Streaming/Channels/101", response="2c5190dceb56f138647aec1613a20e00"
User-Agent: LibVLC/2.2.1 (LIVE555 Streaming Media v2014.07.25)
Accept: application/sdp
应答
RTSP/1.0 200 OK
CSeq: 4
Content-Type: application/sdp
Content-Base: rtsp://10.10.14.168:554/Streaming/Channels/101/
Content-Length: 599
v=0
o=- 1628952899656592 1628952899656592 IN IP4 10.10.14.168
s=Media Presentation
e=NONE
b=AS:5050
t=0 0
a=control:rtsp://10.10.14.168:554/Streaming/Channels/101/
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:5000
a=recvonly
a=x-dimensions:1920,1080
a=control:rtsp://10.10.14.168:554/Streaming/Channels/101/trackID=1
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AKpY1QPAET8s3AQEBQAABwgAAV+QB,aO48gA==
a=Media_header:MEDIAINFO=494D4B48010200000400000100000000000000000000000000000000000000000000000000000000;
a=appversion:1.0
步骤三:SETUP
请求
SETUP rtsp://10.10.14.168:554/Streaming/Channels/101/trackID=1 RTSP/1.0
CSeq: 5
Authorization: Digest username="admin", realm="IP Camera(C3459)", nonce="092598ce6a93eb21c740b7cac1acd5aa", uri="rtsp://10.10.14.168:554/Streaming/Channels/101/", response="cf6da6567716219d8f9585be758d94c7"
User-Agent: LibVLC/2.2.1 (LIVE555 Streaming Media v2014.07.25)
Transport: RTP/AVP/TCP;unicast;interleaved=0-1
应答
RTSP/1.0 200 OK
CSeq: 5
Session: 900278855;timeout=60
Transport: RTP/AVP/TCP;unicast;interleaved=0-1;ssrc=3c7c54df;mode="play"
Date: Sat, Aug 14 2021 14:54:59 GMT
步骤四:Play
请求
PLAY rtsp://10.10.14.168:554/Streaming/Channels/101/ RTSP/1.0
CSeq: 6
Authorization: Digest username="admin", realm="IP Camera(C3459)", nonce="092598ce6a93eb21c740b7cac1acd5aa", uri="rtsp://10.10.14.168:554/Streaming/Channels/101/", response="92fac6580bec31626a34e2a0222bd856"
User-Agent: LibVLC/2.2.1 (LIVE555 Streaming Media v2014.07.25)
Session: 900278855
Range: npt=0.000-
应答
RTSP/1.0 200 OK
CSeq: 6
Session: 900278855
RTP-Info: url=rtsp://10.10.14.168:554/Streaming/Channels/101/trackID=1;seq=15082;rtptime=2002916762
Date: Sat, Aug 14 2021 14:54:59 GMT
信令总结:
1、CSeq序号为自增长
2、Play请求的Session为SetUp应答的Session
3、鉴权有两种分别为Basic和Digest,当返回401的时候,需要带上鉴权信息
rtsp鉴权
Basic鉴权
Basic鉴权计算方法
将`用户名:密码`采用base64处理之后的数据携带在rtsp的Authorization里面。
Digest鉴权
Digest鉴权计算方法
在401的应答中,提取出realm和nonce的值,在通过下面公式计算出response
response= md5(md5(username:realm:password):nonce:md5(public_method:url));
H264Rtp打包
rtp的媒体数据为RTP头+H264数据组成,如下图:
rtp数据格式
RTP头
rtp头格式
version (V):2 bits
padding (P):1 bit 当为1时最后一个字节的 padding有一个计数器,
计算数据需要减去最后计算器的数量,计算器数量包含自己
extension (X):1 bit
CSRC count (CC):4 bits
marker (M):1 bit
payload type (PT):7 bits 媒体类型
sequence number:16 bits 每发送一个 RTP 数据报文序列号值加一
timestamp:32 bits 时间戳
RTP解析示例
H264打包
RTP数据包的H264数据第一个字节定义如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
F: 1 个比特.值为0
NRI: 2 个比特
Type: 5 个比特
Type定义如下
0 没有定义
1-23 NAL单元 单个 NAL 单元包.
24 STAP-A 单一时间的组合包
25 STAP-B 单一时间的组合包
26 MTAP16 多个时间的组合包
27 MTAP24 多个时间的组合包
28 FU-A 分片的单元
29 FU-B 分片的单元
这里只研究1-23和28的情况,其他不考虑。
NAL单元(1-23)
Nal单元组装
只需要获取到数据加上nal头即可(00 00 00 01)
FU-A(分片单元)
分片单元还需要研究第二个字节
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
S: 开始位 1字节 。当设置成1,指示分片NAL单元的开始。其他为0
E:结束位 1字节。当设置成1,指示分片NAL单元的结束。其他为0
R:预留位 1字节
Type:上面的Nul单元类型 在1-23之间
FU-A数据组成
即在组合的时候,不仅需要增加nal头即可(00 00 00 01),还需要根据H264数据的前面两个字节生成新一个新的单字节F+NRI+Type。另外组成数据还需要去掉前面两个字节。
TCP传输
tcp传输格式
tcp传输rtp包,还需要增加4个字节
$: 0x24 固定值
通道:通道号,一般为0
长度: RTP+H264数据的长度
给一个抓包的截图,直观感受一下:
抓包工具分析
TS客户端实现
实现代码结构图
rtspState:入口类,负责协调信令以及rtp数据处理
rtspTransport:负责发送rtsp的信令以及接收应答和rtp数据
rtpH264Parser:负责从rtp包里面解析出H264数据
这里不介绍代码细节,重点讲一下一个思想的实现。我期望在发送请求的函数里面处理应答过来的数据。
调用逻辑图
以发送Options为例,我的处理如下:
private sendOption() {
let params: Map<string, any> = new Map()
this.sendRequest(
EnRtspState[EnRtspState.OPTIONS],
this.RtspUrl,
params,
''
).then((value: ResponseStateData) => {
this.onOption(value)
})
}
我直接在请求里面也将应答处理掉了,这样代码是不是一目了然。
我在sendRequest函数里面返回了一个Promise。
return new Promise<ResponseStateData>((resolve) => {
this.ResponseStateResolev = resolve
})
在Promise里面将resolve函数保存起来,当有应答的时候,在调用这个resolve。调用函数如下:
if (200 == parsed.code) {
resolevData.state = EnResponseState.state200
this.ResponseStateResolev(resolevData)
this.ResponseStateResolev = null
return