当RTSP客户端向RTSP服务端发送DESCRIBE命令时,服务端理应当回复一条SDP报文。
该SDP报文中包含RTSP服务端的基本信息、所能提供的音视频媒体类型以及相应的负载能力,以下是一段SDP示例:
RTSP/1.0 200 OK
Server: VLC/2.1.6
Date: Sun, 06 Dec 2015 11:51:38 GMT
Content-Type: application/sdp
Content-Base: rtsp://127.0.0.1:554/ansersion
Content-Length: 572
Cache-Control: no-cache
Cseq: 2
v=0
o=- 15712671843665285047 15712671843665285047 IN IP4 localhost.localdomain
s=Unnamed
i=N/A
c=IN IP4 0.0.0.0
t=0 0
a=tool:vlc 2.1.6
a=recvonly
a=type:broadcast
a=charset:UTF-8
a=control:rtsp://127.0.0.1:554/ansersion
m=audio 0 RTP/AVP 14
b=AS:128
b=RR:0
a=rtpmap:14 MPA/90000
a=control:rtsp://127.0.0.1:554/ansersion/trackID=0
m=video 0 RTP/AVP 96
b=RR:0
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=64000c;sprop-parameter-sets=Z2QADKzZQUH7ARAAAGewAA/DqPFCmWA=,aOvjyyLA;
a=control:rtsp://127.0.0.1:554/ansersion/trackID=1
对于myRTSPClient,所有的SDP报文均在“int RtspClient::ParseSDP(string SDP)”中被处理,并将这些信息记录进MediaSessionMap中,以便后续建立媒体流连接时使用。截至myRtspClient-1.2.3,该函数仅处理了关于SDP报文中关于音视频的部分信息(即以上示例中红色字体部分)。有关SDP的更多意义,可参见RFC4566。
一、int RtspClient::ParseSDP(string SDP)实现流程
SDP报文有个特点,即每行均为“x=yyy”的形式,我们称x为Key, “yyy”为Value。Key为SDP中的关键字,有着特定意义(可参见RFC4566)。Value则为Key的具体参数。
所以本函数的实现策略就是逐行提取报文中的Key和Value,并将其对应保存至MediaSessionMap中,逐行提取使用的是Regex.RegexLine正则表达式接口。有些Value比较复杂,需要进一步提取数据,则使用Regex.Regex正则表达式接口。
本函数主要提取
(1) m=***
(2) a=***
m指明了媒体流(media)的类型(audio/video)等参数;
a指明了媒体流的属性(attribute),如编码格式(比如H264)等。
解析完SDP后,再检查一下各媒体流基本信息是否有效(MediaInfoCheck)。
代码如下
1 int RtspClient::ParseSDP(string SDP) 2 { 3 MyRegex Regex; 4 string Response(""); 5 int Result = 0; // don't have meaning yet 6 7 if(SDP.length() != 0) Response.assign(SDP); 8 else if(RtspResponse.length() != 0) Response.assign(SDPStr); 9 else return Result; 10 11 string Pattern = "([a-zA-Z])=(.*)"; 12 list<string> Group; 13 bool CollectMediaInfo = false; 14 string CurrentMediaSession(""); 15 while(Regex.RegexLine(&Response, &Pattern, &Group)) { 16 string Key(""), Value(""); 17 if(!Group.empty()) { 18 Group.pop_front(); // pop the line 19 Group.pop_front(); // pop the matched part 20 Key.assign(Group.front()); Group.pop_front(); 21 Value.assign(Group.front()); Group.pop_front(); 22 // SDPInfo->insert(pair<string, string>(Key, Value)); 23 } 24 if(Key == "m") CollectMediaInfo = true; 25 if(Key == "s") CollectMediaInfo = false; 26 if(!CollectMediaInfo) continue; 27 28 if(Key == "m") { 29 /* Pattern: (MediaType) +(Ports) +(Protocol) +(PayloadType)" 30 Example: "(audio) (0) (RTP/AVP) (14)" 31 */ 32 // string PatternTmp("([a-zA-Z]+) +.+ +(.+) +.*"); 33 string PatternTmp("([a-zA-Z]+) +([0-9/]+) +([A-Za-z/]+) +\\b([0-9]+)\\b"); 34 if(!Regex.Regex(Value.c_str(), PatternTmp.c_str(), &Group)) { 35 continue; 36 } 37 Group.pop_front(); 38 CurrentMediaSession.assign(Group.front()); 39 Group.pop_front(); 40 Group.pop_front(); // FIXME: Ports are ignored 41 string Protocol(Group.front()); 42 Group.pop_front(); 43 int PayloadTypeTmp = -1; 44 stringstream ssPayloadType; 45 ssPayloadType << Group.front(); 46 ssPayloadType >> PayloadTypeTmp; 47 48 MediaSession NewMediaSession; 49 NewMediaSession.MediaType.assign(CurrentMediaSession); 50 NewMediaSession.Protocol.assign(Protocol); 51 NewMediaSession.PayloadType.push_back(PayloadTypeTmp); 52 (*MediaSessionMap)[CurrentMediaSession] = NewMediaSession; 53 54 } 55 if("a" == Key) { 56 string PatternRtpmap("rtpmap:.* +([0-9A-Za-z]+)/([0-9]+)"); 57 string PatternFmtp_H264("fmtp:.*sprop-parameter-sets=([A-Za-z0-9+/=]+),([A-Za-z0-9+/=]+)"); 58 string PatternFmtp_H265("fmtp:.*sprop-vps=([A-Za-z0-9+/=]+);.*sprop-sps=([A-Za-z0-9+/=]+);.*sprop-pps=([A-Za-z0-9+/=]+)"); 59 string PatternControl("control:(.+)"); 60 if(CurrentMediaSession.length() == 0) { 61 continue; 62 } 63 if(Regex.Regex(Value.c_str(), PatternRtpmap.c_str(), &Group)) { 64 Group.pop_front(); 65 (*MediaSessionMap)[CurrentMediaSession].EncodeType = Group.front();; 66 Group.pop_front(); 67 stringstream TimeRate; 68 TimeRate << Group.front(); 69 TimeRate >> (*MediaSessionMap)[CurrentMediaSession].TimeRate; 70 } else if(Regex.Regex(Value.c_str(), PatternControl.c_str(), &Group)) { 71 Group.pop_front(); 72 string ControlURITmp(""); 73 /* 'Value' could be 74 * 1: "rtsp://127.0.0.1/ansersion/track=1" 75 * 2: "track=1" 76 * If is the '2', it should be prefixed with the URI. */ 77 if(!Regex.Regex(Value.c_str(), "rtsp://")) { 78 ControlURITmp += RtspURI; 79 ControlURITmp += "/"; 80 } 81 ControlURITmp += Group.front(); 82 printf("Control: %s\n", ControlURITmp.c_str()); 83 (*MediaSessionMap)[CurrentMediaSession].ControlURI.assign(ControlURITmp); 84 } else if(Regex.Regex(Value.c_str(), PatternFmtp_H264.c_str(), &Group)) { 85 Group.pop_front(); 86 SPS.assign(Group.front()); 87 Group.pop_front(); 88 PPS.assign(Group.front()); 89 90 if(Regex.Regex(Value.c_str(), "packetization-mode=([0-2])", &Group)) { 91 Group.pop_front(); 92 stringstream PacketizationMode; 93 PacketizationMode << Group.front(); 94 PacketizationMode >> (*MediaSessionMap)[CurrentMediaSession].Packetization; 95 } 96 } else if(Regex.Regex(Value.c_str(), PatternFmtp_H265.c_str(), &Group)) { 97 Group.pop_front(); 98 VPS.assign(Group.front()); 99 Group.pop_front(); 100 SPS.assign(Group.front()); 101 Group.pop_front(); 102 PPS.assign(Group.front()); 103 } 104 } 105 } 106 107 for(map<string, MediaSession>::iterator it = MediaSessionMap->begin(); it != MediaSessionMap->end(); it++) { 108 it->second.MediaInfoCheck(); 109 } 110 111 return Result; 112 }