本文会详细介绍RTSP相关知识,包括数据格式,交互过程,以及使用Node.js实现RTSP客户端
RTSP
rtsp,英文全称 Real Time Streaming Protocol,RFC2326,实时流传输协议,是TCP/IP协议体系中的一个应用层协议!协议主要规定定了一对多应用程序如何有效地通过IP网络传送多媒体数据。RTSP体系结位于RTP和RTCP之上(RTCP用于控制传输,RTP用于数据传输),使用TCP或UDP完成数据传输!
RTSP数据格式
RTSP协议格式与HTTP协议格式类似,明文传输
- RTSP请求格式:
method url vesion\r\n
CSeq: x\r\n
xxx\r\n
...
\r\n
method
:方法,表明这次请求的方法,rtsp定义了很多方法,下文会介绍。url
:格式一般为rtsp://ip:port/session
,ip表主机ip,port表端口好,如果不写那么就是默认端口,rtsp的默认端口为554,session表明请求哪一个会话version
: 表示rtsp的版本,现在为RTSP/1.0
CSeq
: 序列号,每个RTSP请求和响应都对应一个序列号,序列号是递增的,简单理解是请求头Header的属性,还有像Host等字段,具体可以参考RFC文档- RTSP响应格式:
vesion 200 OK\r\n
CSeq: x\r\n
xxx\r\n
...
\r\n
- version:表示rtsp的版本,现在为RTSP/1.0
- CSeq:序列号,这个必须与对应请求的序列号相同
RTSP交互过程
下面是一个简单的RTSP交互过程:
接下来依次介绍使用到的请求方法与响应示例。(TODO: GET PARAMETER, PAUSE)
- OPTIONS
C–>S
OPTIONS rtsp://127.0.0.1:1051/live RTSP/1.0\r\n
CSeq: 2\r\n
\r\n
客户端向服务器请求可用方法
S–>C
RTSP/1.0 200 OK\r\n
CSeq: 2\r\n
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY\r\n
\r\n
服务端回复客户端,当前可用方法OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY
- DESCRIBE
C–>S
DESCRIBE rtsp://127.0.0.1:1051/live RTSP/1.0\r\n
CSeq: 3\r\n
Accept: application/sdp\r\n
\r\n
客户端向服务器请求媒体描述文件,格式为sdp
S–>C
RTSP/1.0 200 OK\r\n
CSeq: 3\r\n
Content-length: 146\r\n
Content-type: application/sdp\r\n
\r\n
v=0\r\n
o=- 91565340853 1 in IP4 127.0.0.1\r\n
t=0 0\r\n
a=contol:*\r\n
m=video 0 RTP/AVP 96\r\n
a=rtpmap:96 H264/90000\r\n
a=framerate:25\r\n
a=control:track0\r\n
服务器回复了sdp文件,这个文件告诉客户端当前服务器有哪些音视频流,有什么属性,下文有介绍
这里只需要知道客户端可以根据这些信息得知有哪些音视频流可以发送
- SETUP
C–>S
SETUP rtsp://127.0.0.1:1051/live/track0 RTSP/1.0\r\n
CSeq: 4\r\n
Transport: RTP/AVP;unicast;client_port=54492-54493\r\n
\r\n
客户端发送建立请求,请求建立连接会话,准备接收音视频数据
解析一下Transport: RTP/AVP;unicast;client_port=54492-54493\r\n
RTP/AVP:表示RTP通过UDP发送,如果是RTP/AVP/TCP则表示RTP通过TCP发送
unicast:表示单播,如果是multicast则表示多播
client_port=54492-54493:由于这里希望采用的是RTP OVER UDP,所以客户端发送了两个用于传输数据的端口,客户端已经将这两个端口绑定到两个udp套接字上,54492表示是RTP端口,54493表示RTCP端口(RTP端口为某个偶数,RTCP端口为RTP端口+1)
S–>C
RTSP/1.0 200 OK\r\n
CSeq: 4\r\n
Transport: RTP/AVP;unicast;client_port=54492-54493;server_port=56400-56401\r\n
Session: 66334873\r\n
\r\n
服务端接收到请求之后,得知客户端要求采用RTP OVER UDP发送数据,单播,客户端用于传输RTP数据的端口为54492,
RTCP的端口为54493服务器也有两个udp套接字,绑定好两个端口,一个用于传输RTP,一个用于传输RTCP,
这里的端口号为56400-56401之后客户端会使用54492-54493这两端口和服务器通过udp传输数据,
服务器会使用56400-56401这两端口和这个客户端传输数据
- PLAY
C–>S
PLAY rtsp://127.0.0.1:1051/live RTSP/1.0\r\n
CSeq: 5\r\n
Session: 66334873\r\n
Range: npt=0.000-\r\n
\r\n
客户端请求播放媒体
S–>C
RTSP/1.0 200 OK\r\n
CSeq: 5\r\n
Range: npt=0.000-\r\n
Session: 66334873; timeout=60\r\n
\r\n
服务器回复之后,会开始使用RTP通过udp向客户端的54492端口发送数据
TEARDOWN
C–>S
TEARDOWN rtsp://192.168.31.115:8554/live RTSP/1.0\r\n
CSeq: 6\r\n
Session: 66334873\r\n
\r\n
S–>C
RTSP/1.0 200 OK\r\n
CSeq: 6\r\n
\r\n
RTSP请求头字段
- CSeq
CSeq字段指定RTSP请求-响应对的序列号。 这个字段必须出现在所有的请求和响应中。 对于每个包含给定序列号的RTSP请求,将会有一个具有相同序列号的相应响应。
SDP协议
Session Description Protoco,会话描述协议,用于描述多媒体会话
RFC4566
在上文rtsp交互流程中,DESCRIBE
请求会获取sdp报文,下面来分析:
格式
SDP会话描述由一个会话级描述(session_level description)和多个媒体级描述(media_level description)组成。
=
其中:
:属性(大小写敏感),例如v
代表版本;
:内容,它是结构化文本,对应的格式和属性关联,采用 UTF8 编码;=
:符号,两边不能存在空格;=*
:表示可选。
会话描述参数
- 会话级(session_level)的作用域是整个会话。其位置是从’v=’行开始到下一个媒体描述为止。
- 媒体级(media_level)描述是对单个的媒体流进行描述,其位置是从’m=’行开始到下一个媒体描述为止。
- Version(必选)
协议版本,不包括次版本号,格式如下:
v=0
- origion(必选)
对会话的发起者进行了描述
o=
:用户的登录名。如果主机不支持
,则为 ”-”。注意:
不能含空格。
::会话ID。在整个会话中,必须是唯一的
:该会话公告的版本,供公告代理服务器检测同一会话的若干个公告哪个是最新公告.基本要求是会话数据修改后该版本值递增,建议用NTP时戳。
:网络类型,一般为”IN”,表示”internet”:地址类型,一般为IP4
:地址
- Session Name(必选)
会话名称,在整个会话中有且只有一个”s=”
s=
4. Connnection Data(可选)
表示媒体的连接信息, 一个会话声明中,会话级描述中必须有”c=”项或者在每个媒体级描述中有一个”c=”项。也有可能在会话级描述和每个媒体级描述中都有”c=”项。
c=
5. Bandwidth(可选)
带宽信息,单位kilobits per second
b=:
:包括两种CT和AS。CT:ConferenceTotal,总带宽。AS:Application-SpecificMaximum,单个媒体带宽的最大值
- Times(必选)
描述了会话的开始时间和结束时间
t=
和
为NTP时间,单位是秒。假如
为零,表示过了
时间后会话一直持续。当
和
均为零时表示持久会话。建议start time和stoptime不要设为0。因为不知道此会话的开始和结束时间,增加了调度(scheduling)的难度。
- Media Announcement(必选)
媒体名称和传输地址。一个媒体描述以”m=”开始到下一个”m=”结束。
m=
:表示媒体类型。有"audio"、 "video"、"application"(例白板信息)、"data"(不向用户显示的数据)和"control"(描述额外的控制通道)。
:媒体流发往传输层的端口。取决于c=行规定的网络类型和接下来的传送层协议:对UDP为1024-65535;对于RTP为偶数。当分层编码流被发送到一个单播地址时,需要列出多个端口。
对于RTP,偶数端口被用来传输数据,奇数端口用来传输RTCP包。例:
/*
端口49170和49171为第一对RTP/RTCP端口,49172和49173为第二对的端口。传输协议是RTP/AVP,媒体格式为31(媒体格式是rtp头中payload参数对应的)
*/
m=video49170/2 RTP/AVP 31
:传输协议,与c=行的地址类型有关。两种: RTP/AVP,表示RealtimeTransport Protocol using the Audio/Video profile carried over UDP;
:媒体格式。对于音频和视频就是在RTP Audio/Video Profile定义的负载类型(payload type)。但第一个为缺省值,分为静态绑定和动态绑定:静态绑定即媒体编码方式与RTP负载类型有确定的一一对应关系,动态绑定即媒体编码方式(如时钟频率,音频信道数等)没有完全确定,需要进一步的属性说明(用rtpmap)。静态绑定举例如下:
//u_law的PCM编码单信道Audio,采样率8KHZ。在RTPAudio/Video profile中对应的payload type为0
m=audio49232 RTP/AVP 0
动态绑定的例子:16位线形编码,采样率为16KHZ,假如我们希望动态RTP/AVP 类型98表示此此流,写法如下:
m=video49232 RTP/AVP 98 a=rtpmap:98 L16/16000/2
- rtpmap(可选)
0 个或多个会话属性行
a=rtpmap:/[/]
v=0\r\n
o=- 0 0 IN IP4 127.0.0.1\r\n
s=Stream\r\n
c=IN IP4 0.0.0.0\r\n
t=0 0\r\n
m=video 0 RTP/AVP 96\r\n
a=rtpmap:96 H264/90000\r\n
a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z01AKvQFAX/LCAAAAwAIAAADAYAg,aO4SiA==; profile-level-id=4D402A\r\n
a=control:trackID=0\r\n' +
m=audio 0 RTP/AVP 97\r\n' +
a=rtpmap:97 mpeg4-generic/22050/1\r\n
a=fmtp:97 profile-level-id=1; mode=AAC-hbr; sizelength=13; indexlength=3; indexdeltalength=3; config=138856e500\r\n
a=control:trackID=1\r\n
m行属性信息,其中video代表视频,0代表传输视频的rtp端口为0,RTP/AVP中AVP代表音视频配置
后面的数字96表示不同视频编码类型(RTP传输视频时,RTP头中的payloadType);视频发送方按照对方声明对应的编码类型的PT值来发送RTP;
a属性字段较多,这里以a=rtpmap:96 H264/90000这行为例,rtpmap后面的数字96,表示本编码类型声明的rtp头中的paylaodType值,H264表示码流是H264编码,90000表示采样率为90000HZ(H264的采样率必须是90000HZ);
a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z01AKvQFAX/LCAAAAwAIAAADAYAg,aO4SiA==; profile-level-id=4D402A
其中96同rtpmap中的值,rtpmap中和fmtp中的数字值相同,表示他们描述的是同一种编码类型.这里来介绍下fmtp中的几个关键值:
JS实现SDP解析器(TODO)
RTSP数据封装格式 - UDO方式(TODO)
RTSP数据封装格式 - TCP方式
RTP数据报等数据采用ASCII 符号$(0x24)固定开头,后面跟着一字节大小的 channel identifier,以及2字节数据代表RTP包的大小。紧跟着就是RTP包数据,最后以CRLF
结尾。
客户端解析请求格式示例
- Option
{
protocolVersion: 'RTSP/1.0',
statusCode: 200,
statusMessage: 'OK',
messageSize: 128,
headersSize: 103,
bodySize: 0,
headers: [
{ name: 'CSeq', values: [Array] },
{ name: 'Public', values: [Array] }, // 服务端可用的方法
{ name: 'Server', values: [Array] }
]
}
- Describe
{
protocolVersion: 'RTSP/1.0',
statusCode: 200,
statusMessage: 'OK',
messageSize: 595,
headersSize: 117,
bodySize: 449,
headers: [
{ name: 'CSeq', values: [Array] },
{ name: 'Content-Base', values: [Array] },
{ name: 'Content-Length', values: [Array] },
{ name: 'Content-Type', values: [Array] },
{ name: 'Server', values: [Array] }
],
body: {
contentType: 'application/sdp',
// DESCRIBE是客户端向服务器请求媒体信息,这是服务器需要回复sdp描述文件,
plain: 'v=0\r\n' +
'o=- 0 0 IN IP4 127.0.0.1\r\n' +
's=Stream\r\n' +
'c=IN IP4 0.0.0.0\r\n' +
't=0 0\r\n' +
'm=video 0 RTP/AVP 96\r\n' +
'a=rtpmap:96 H264/90000\r\n' +
'a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAH6xyBEFwUbARAAADAAEAAAMAYA8YMYRg,aOhDssiw; profile-level-id=64001F\r\n' +
'a=control:trackID=0\r\n' +
'm=audio 0 RTP/AVP 97\r\n' +
'a=rtpmap:97 mpeg4-generic/48000/2\r\n' +
'a=fmtp:97 profile-level-id=1; mode=AAC-hbr; sizelength=13; indexlength=3; indexdeltalength=3; config=119056e500\r\n' +
'a=control:trackID=1\r\n'
}
}
- Setup
SETUP是客户端请求建立会话连接, 传输头部(Transport header)详细列出了客户端能接受的数据传输参数 ;响应中会包含服务器选定的传输参数。
{
protocolVersion: 'RTSP/1.0',
statusCode: 200,
statusMessage: 'OK',
messageSize: 165,
headersSize: 138,
bodySize: 0,
headers: [
{ name: 'CSeq', values: [Array] },
{ name: 'Server', values: [Array] },
{ name: 'Session', values: [Array] },
{ name: 'Transport', values: [Array] }
]
}
相关代码:
const RTSPClient = require("./RTSPClient")
const SemanticSDP = require("semantic-sdp");
async function main() {
const client = new RTSPClient()
client.on("connected", function() {
console.log("connected")
})
client.on("data", function(buffer) {
console.log(buffer)
})
await client.connect("rtsp://localhost:8554/mystream");
const options = await client.options()
const describe = await client.describe()
const sdp = describe.body.plain
var offer = SemanticSDP.SDPInfo.process(sdp);
let videoOffer = offer.getMedia("video");
console.log(describe)
const setup = await client.setup(videoOffer.control, "RTP/AVP/TCP;unicast;interleaved=0-1")
console.log('setup', setup)
const sessionId = parseInt(setup.headers[2].values[0].value)
client.setSession(sessionId)
const play = await client.play()
console.log('play', play)
}
main()
实现
我基于node.js实现了一个RTSP客户端,欢迎start和提issue。