前言

rtsp(Real Time Streaming Protocol)协议可以实现音视频的实时传输。安防摄像机标配之一就是支持rtsp协议。本文主要从下面几个方面对rtsp协议进行介绍:

1、rtsp协议总览

2、rtsp信令交互流程

3、rtsp鉴权

4、H264的rtp打包

5、tcp如何传输rtp

6、typescript的客户端实现

rtsp协议总览



基于typescript的rtsp客户端实现_http

RTSP相关规范

RFC2326:主要介绍rtsp的信令的交互流程以及鉴权,还包含了TCP/UDP传输rtp包。

RFC3984:主要介绍了H264如何打包到rtp包里面

RFC4566:SDP规范的介绍

RFC3550:RTP头的介绍

rtsp信令交互




基于typescript的rtsp客户端实现_推荐系统_02

最简单的信令交互

前面四步都是信令交互,步骤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鉴权

基于typescript的rtsp客户端实现_网络通信_03

Basic鉴权计算方法

将`用户名:密码`采用base64处理之后的数据携带在rtsp的Authorization里面。

Digest鉴权

基于typescript的rtsp客户端实现_推荐系统_04

Digest鉴权计算方法

在401的应答中,提取出realm和nonce的值,在通过下面公式计算出response

response= md5(md5(username:realm:password):nonce:md5(public_method:url));

H264Rtp打包



rtp的媒体数据为RTP头+H264数据组成,如下图:

基于typescript的rtsp客户端实现_wireshark_05

rtp数据格式

RTP头

基于typescript的rtsp客户端实现_http_06

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

基于typescript的rtsp客户端实现_rpc_07

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)



基于typescript的rtsp客户端实现_wireshark_08

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之间

基于typescript的rtsp客户端实现_http_09

FU-A数据组成

即在组合的时候,不仅需要增加nal头即可(00 00 00 01),还需要根据H264数据的前面两个字节生成新一个新的单字节F+NRI+Type。另外组成数据还需要去掉前面两个字节。

TCP传输



基于typescript的rtsp客户端实现_wireshark_10

tcp传输格式

tcp传输rtp包,还需要增加4个字节

$: 0x24 固定值

通道:通道号,一般为0

长度: RTP+H264数据的长度

给一个抓包的截图,直观感受一下:

基于typescript的rtsp客户端实现_rpc_11

抓包工具分析

TS客户端实现



基于typescript的rtsp客户端实现_wireshark_12

实现代码结构图

rtspState:入口类,负责协调信令以及rtp数据处理

rtspTransport:负责发送rtsp的信令以及接收应答和rtp数据

rtpH264Parser:负责从rtp包里面解析出H264数据

这里不介绍代码细节,重点讲一下一个思想的实现。我期望在发送请求的函数里面处理应答过来的数据。

基于typescript的rtsp客户端实现_http_13

调用逻辑图

以发送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