一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(四)用户接口层之处理SDP报文
当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)。
代码如下
int RtspClient::ParseSDP(string SDP)
{
MyRegex Regex;
string Response("");
int Result = ; // don't have meaning yet if(SDP.length() != ) Response.assign(SDP);
else if(RtspResponse.length() != ) Response.assign(SDPStr);
else return Result; string Pattern = "([a-zA-Z])=(.*)";
list<string> Group;
bool CollectMediaInfo = false;
string CurrentMediaSession("");
while(Regex.RegexLine(&Response, &Pattern, &Group)) {
string Key(""), Value("");
if(!Group.empty()) {
Group.pop_front(); // pop the line
Group.pop_front(); // pop the matched part
Key.assign(Group.front()); Group.pop_front();
Value.assign(Group.front()); Group.pop_front();
// SDPInfo->insert(pair<string, string>(Key, Value));
}
if(Key == "m") CollectMediaInfo = true;
if(Key == "s") CollectMediaInfo = false;
if(!CollectMediaInfo) continue; if(Key == "m") {
/* Pattern: (MediaType) +(Ports) +(Protocol) +(PayloadType)"
Example: "(audio) (0) (RTP/AVP) (14)"
*/
// string PatternTmp("([a-zA-Z]+) +.+ +(.+) +.*");
string PatternTmp("([a-zA-Z]+) +([0-9/]+) +([A-Za-z/]+) +\\b([0-9]+)\\b");
if(!Regex.Regex(Value.c_str(), PatternTmp.c_str(), &Group)) {
continue;
}
Group.pop_front();
CurrentMediaSession.assign(Group.front());
Group.pop_front();
Group.pop_front(); // FIXME: Ports are ignored
string Protocol(Group.front());
Group.pop_front();
int PayloadTypeTmp = -;
stringstream ssPayloadType;
ssPayloadType << Group.front();
ssPayloadType >> PayloadTypeTmp; MediaSession NewMediaSession;
NewMediaSession.MediaType.assign(CurrentMediaSession);
NewMediaSession.Protocol.assign(Protocol);
NewMediaSession.PayloadType.push_back(PayloadTypeTmp);
(*MediaSessionMap)[CurrentMediaSession] = NewMediaSession; }
if("a" == Key) {
string PatternRtpmap("rtpmap:.* +([0-9A-Za-z]+)/([0-9]+)");
string PatternFmtp_H264("fmtp:.*sprop-parameter-sets=([A-Za-z0-9+/=]+),([A-Za-z0-9+/=]+)");
string PatternFmtp_H265("fmtp:.*sprop-vps=([A-Za-z0-9+/=]+);.*sprop-sps=([A-Za-z0-9+/=]+);.*sprop-pps=([A-Za-z0-9+/=]+)");
string PatternControl("control:(.+)");
if(CurrentMediaSession.length() == ) {
continue;
}
if(Regex.Regex(Value.c_str(), PatternRtpmap.c_str(), &Group)) {
Group.pop_front();
(*MediaSessionMap)[CurrentMediaSession].EncodeType = Group.front();;
Group.pop_front();
stringstream TimeRate;
TimeRate << Group.front();
TimeRate >> (*MediaSessionMap)[CurrentMediaSession].TimeRate;
} else if(Regex.Regex(Value.c_str(), PatternControl.c_str(), &Group)) {
Group.pop_front();
string ControlURITmp("");
/* 'Value' could be
* 1: "rtsp://127.0.0.1/ansersion/track=1"
* 2: "track=1"
* If is the '2', it should be prefixed with the URI. */
if(!Regex.Regex(Value.c_str(), "rtsp://")) {
ControlURITmp += RtspURI;
ControlURITmp += "/";
}
ControlURITmp += Group.front();
printf("Control: %s\n", ControlURITmp.c_str());
(*MediaSessionMap)[CurrentMediaSession].ControlURI.assign(ControlURITmp);
} else if(Regex.Regex(Value.c_str(), PatternFmtp_H264.c_str(), &Group)) {
Group.pop_front();
SPS.assign(Group.front());
Group.pop_front();
PPS.assign(Group.front()); if(Regex.Regex(Value.c_str(), "packetization-mode=([0-2])", &Group)) {
Group.pop_front();
stringstream PacketizationMode;
PacketizationMode << Group.front();
PacketizationMode >> (*MediaSessionMap)[CurrentMediaSession].Packetization;
}
} else if(Regex.Regex(Value.c_str(), PatternFmtp_H265.c_str(), &Group)) {
Group.pop_front();
VPS.assign(Group.front());
Group.pop_front();
SPS.assign(Group.front());
Group.pop_front();
PPS.assign(Group.front());
}
}
} for(map<string, MediaSession>::iterator it = MediaSessionMap->begin(); it != MediaSessionMap->end(); it++) {
it->second.MediaInfoCheck();
} return Result;
}
二、提取媒体流信息
while(Regex.RegexLine(&Response, &Pattern, &Group))
其中Response为SDP字符串,Pattern为正则表达式模板"([a-zA-Z])=(.*)",Group为空的string的list。
RegexLine会进行正则匹配Response的第1行,如果匹配成功,则将匹配内容放入Group这个list中,list的第1项是整行字符串,第2项是匹配上模板的字符串,第3项为Key,第4项为Value,然后将匹配指针指向下一行(下次匹配时就会匹配下一行),并且返回true。如果匹配失败,则返回false。
if(!Group.empty()) {
Group.pop_front(); // pop the line
Group.pop_front(); // pop the matched part
Key.assign(Group.front()); Group.pop_front();
Value.assign(Group.front()); Group.pop_front();
}
if(Key == "m") CollectMediaInfo = true;
保存Key和Value,如果Key为“m”,则将CollectMediaInfo标志置为true,表示可以提取媒体流信息,以及相关属性。
if(Key == "m") {
/* Pattern: (MediaType) +(Ports) +(Protocol) +(PayloadType)"
Example: "(audio) (0) (RTP/AVP) (14)"
*/
string PatternTmp("([a-zA-Z]+) +([0-9/]+) +([A-Za-z/]+) +\\b([0-9]+)\\b");
if(!Regex.Regex(Value.c_str(), PatternTmp.c_str(), &Group)) {
continue;
}
Group.pop_front();
CurrentMediaSession.assign(Group.front());
Group.pop_front();
Group.pop_front(); // FIXME: Ports are ignored
string Protocol(Group.front());
Group.pop_front();
int PayloadTypeTmp = -1;
stringstream ssPayloadType;
ssPayloadType << Group.front();
ssPayloadType >> PayloadTypeTmp;
MediaSession NewMediaSession;
NewMediaSession.MediaType.assign(CurrentMediaSession);
NewMediaSession.Protocol.assign(Protocol);
NewMediaSession.PayloadType.push_back(PayloadTypeTmp);
(*MediaSessionMap)[CurrentMediaSession] = NewMediaSession;
}
提取媒体流的类型、端口、协议类型和Payload,并添加进MediaSessionMap中,索引为媒体流类型(audio/video)
注:截至myRtspClient-1.2.3,单个myRtspClient实例仅支持单一的audio和video媒体会话,即当RTSP服务器提供多种video(或audio)——比如不同码率——媒体流时,myRtspClient仅适用SDP中标识的最后一个。
还有一些提取属性(a=***)的代码,形式与上述雷同,不再赘述。
注:Regex.Regex不同于Regex.RegexLine,匹配成功后,string的list中的第1项为匹配上模板的字符串,第2项为第1个匹配上括号的字符串,以此类推。
三、检查媒体信息
for(map<string, MediaSession>::iterator it = MediaSessionMap->begin(); it != MediaSessionMap->end(); it++) {
it->second.MediaInfoCheck();
}
检查媒体流信息,并打印错误。
一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(四)用户接口层之处理SDP报文的更多相关文章
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(一)概览
myRTSPClient主要可以分成3个部分: 1. RTSPClient用户接口层: 2. RTP 音视频传输解析层: 3. RTP传输层. "RTSPClient用户接口层": ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(六)RTP音视频传输解析层之音视频数据传输格式
一.差异 本地音视频数据格式和用来传输的音视频数据格式存在些许差异,由于音视频数据流到达客户端时,需要考虑数据流的数据边界.分包.组包顺序等问题,所以传输中的音视频数据往往会多一些字节. 举个例子,有 ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(五)用户接口层之提取媒体流数据
当RTSP客户端向RTSP服务端发送完PLAY命令后,RTSP服务端就会另外开启UDP端口(SDP协商定义的端口)发送RTP媒体流数据包.这些数据包之间会间隔一段时间(毫秒级)陆续被发送到RTSP客户 ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(十)使用JRTPLIB传输RTP数据
myRtspClient通过简单修改JRTPLIB的官方例程作为其RTP传输层实现.因为JRTPLIB使用的是CMAKE编译工具,这就是为什么编译myRtspClient时需要预装CMAKE. 该部分 ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——解码篇:(一)用ffmpeg解码视频
一.概述 myRTSPClient(RTSPClient)获取音视频数据之后,接下来的工作便是将音视频数据交给解码器去解码(ffmpeg),ffmpeg解码之后于是便有了呈现在终端用户(USER)面前 ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(二)用户接口层之RtspClient类及其构造函数
RtspClient类是myRTSPClient函数库所有特性集中实现的地方. 主要为用户提供: 1. RTSP协议通信接口函数,如DoOPTIONS(): 2. RTSP账号.密码设置函数,如Set ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(三)用户接口层之RTSP命令
截至版本1.2.3,myRtspClient函数库共支持以下6个RTSP命令: (1)OPTIONS (2)DESCRIBE (3)SETUP (4)PLAY (5)PAUSE (6)TEARDOWN ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——解码篇:(三)一个简单的rtsp播放器
该篇内容简单的将前两篇内容组合在一起,创建了2个线程,分别播放音频和视频. int main(int argc, char * argv[]) { RtspClient Client; pthread ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(八)RTP音视频传输解析层之MPA传输格式
一.MPEG RTP音频传输 相较H264的RTP传输格式,MPEGE音频传输格式则简单许多. 每一包MPEG音频RTP包都前缀一个4字节的Header,如下图(RFC2550) “MBZ”必须为0( ...
随机推荐
- int-整数+bool-布尔功能介绍
int #创建和转换 #a = 123#a = int(123)#转换 #Age = "22"#Age = int(22)#1.当前整数用二进制表示的最小位数 # age = 50 ...
- 请验证实例名称是否正确并且 SQL Server 已配置为允许远程连接。 (provider: Named Pipes Provider, error: 40 - 无法打开到 SQL Server 的连接)
程序异常,错误信息:在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误.未找到或无法访问服务器.请验证实例名称是否正确并且 SQL Server 已配置为允许远程连接. (pro ...
- Python爬虫从入门到放弃(十六)之 Scrapy框架中Item Pipeline用法
当Item 在Spider中被收集之后,就会被传递到Item Pipeline中进行处理 每个item pipeline组件是实现了简单的方法的python类,负责接收到item并通过它执行一些行为, ...
- Python爬虫从入门到放弃(十七)之 Scrapy框架中Download Middleware用法
这篇文章中写了常用的下载中间件的用法和例子.Downloader Middleware处理的过程主要在调度器发送requests请求的时候以及网页将response结果返回给spiders的时候,所以 ...
- IDEA的热部署插件jrebel6.4.3离线安装版配置与破解
JRebel 介绍 IDEA上原生是不支持热部署的,一般更新了 Java 文件后要手动重启 Tomcat 服务器,才能生效,浪费不少生命啊.目前对于idea热部署最好的解决方案就是安装JRebel插件 ...
- CDH版本的oozie安装执行bin/oozie-setup.sh prepare-war,没生成oozie.war?
不多说,直接上干货! 前期博客 Oozie安装部署 问题描述 bin/oozie-setup.sh prepare-war 解决办法 [hadoop@bigdatamaster bin]$ pwd / ...
- HTML5之placeholder属性以及如何更改placeholder属性中文字颜色
今天在群里看到群友问了一个这样的问题,就是如何更改placeholder属性中文字的颜色,以前用过这属性,却是没更改过颜色,于是便试了试,中途遇到些问题,查找资料后特来总结一下. 熟悉HTML5的人应 ...
- Java GC - 监控回收行为与日志分析
1. 简介 在上一篇介绍<Java GC - 垃圾回收机制>, 本文将介绍如何监控 Javc GC 行为,同时涉及一些GUI工具的使用(虽然有些已经很老并不再更新),监控GC在于判断JVM ...
- pwnable input2 之 write up
首先看源代码: input2@ubuntu:~$ cat input.c #include <stdio.h> #include <stdlib.h> #include < ...
- 使用Modelsim进行简单仿真
这里记载一下使用modelsim进行简单的仿真,方便以后使用的时候进行查看.所谓的简单的仿真,就是没有IP核.只用图形界面不用tcl脚本进行的仿真.简单的仿真步骤为: 1.改变路径到工作环境下的路径下 ...