RTSP协议的处理--ANNOUNCE
![](https://img2022.cnblogs.com/blog/2525040/202211/2525040-20221109170037566-2130161757.png)
一、ABLMediaServer的过程
1.ProcessNetData处理数据的开始。
2.FillHttpHeadToStruct解析各个字段。
3.InputRtspData处理具体的方法。
4.GetMediaURLFromRtspSDP函数中
szCurRtspURL为:rtsp://192.168.169.131:554/live/Camera_00001
5.GetRtspPathCount传入的szCurRtspURL
(1)返回的为有/的个数,即有app和stream才合法。
(2)填充这个结构 的“app[128];//添加流的应用名”和 “char stream[128];//添加流的id ”
//代理拉流转发参数结构 struct addStreamProxyStruct { char secret[256];//api操作密码 char vhost[64];//添加流的虚拟主机 char app[128];//添加流的应用名 char stream[128];//添加流的id char url[512];//拉流地址 ,支持 rtsp\rtmp\http-flv \ hls char isRtspRecordURL[128];//是否rtsp录像回放 char enable_mp4[64];//是否录像 addStreamProxyStruct() { memset(secret, 0x00, sizeof(secret)); memset(vhost, 0x00, sizeof(vhost)); memset(app, 0x00, sizeof(app)); memset(stream, 0x00, sizeof(stream)); memset(url, 0x00, sizeof(url)); memset(isRtspRecordURL, 0x00, sizeof(isRtspRecordURL)); memset(enable_mp4, 0x00, sizeof(enable_mp4)); } };
6.GetMediaStreamSource
(1)从xh_ABLMediaStreamSourceMap查找是否有这个流。
(2)xh_ABLMediaStreamSourceMap这个变量的定义。
typedef boost::shared_ptr<CMediaStreamSource> CMediaStreamSource_ptr;
typedef boost::unordered_map<string, CMediaStreamSource_ptr> CMediaStreamSource_ptrMap;
CMediaStreamSource_ptrMap xh_ABLMediaStreamSourceMap;
7.创建原始媒体流 pMediaSource = CreateMediaStreamSource(szMediaSourceURL,nClient, MediaSourceType_LiveMedia, 0);
(1)szMediaSourceURL参数如/live/Camera_00001,即app+stream。
(2)在CreateMediaStreamSource主要调用创建一个流,
pXHClient = boost::make_shared<CMediaStreamSource>(szURL,nClient, nSourceType, nDuration);
(3)CMediaStreamSource的构造函数对流进行初始化,对传入的参数szURL(szMediaSourceURL),解析为app、stream。
(4)创建的pXHClient插入到xh_ABLMediaStreamSourceMap。
xh_ABLMediaStreamSourceMap.insert(std::make_pair(strURL, pXHClient));
8.在对ANNOUNCE进行回重以后,开始解析sdp.
9.GetMediaInfoFromRtspSDP从sdp信息中获取视频、音频格式信息 ,根据这些信息创建rtp解包、rtp打包
(1)没有解析会话描述。
(2)通过标识m=video和m=audio来区分视频和音频的描述 。
(3)视频和音频描述的分析通过CABLSipParse类。
(4)获取视频编码名称
- 通过解析器,得到a=rtpmap的value,如a=rtpmap:96 H264/90000
- 通过空格得到动态负载类型nVideoPayload和nAudioPayload即96和97
- 通过/得到编码名称szVideoName和szAudioName即H264和MPEG4-GENERIC
- 在音频描述中,通过/得到通过号及时钟频率及通道号。
10.CABLSipParse把SDP头全部装入map里面,尽可能找出详细的项数据
(1)重要的数据结构
1 struct SipFieldStruct
2 {
3 char szKey[384];
4 char szValue[1024*16];
5 SipFieldStruct()
6 {
7 memset(szKey, 0x00, sizeof(szKey));
8 memset(szValue, 0x00, sizeof(szValue));
9 }
10 };
(2)重要的变量
typedef map<string, SipFieldStruct*, less<string> > SipFieldStructMap;
(3)首先根据“\r\n”取出每行的数据。
(4)第一行,如“m=video 0 RTP/AVP 96”,通过空格,将key和value分开。
(5)其他行,如“b=AS:2457”,通过“:”,将key和value分开。
(6)插入mpa时,结构体的key为key, 结构体为value.
(7)把方法作为一个关键字,KEY,存储下来。这时key为“Method”,原结构的value变为key,然后插入map.
(8)如value有更多的信息,将作为了键插入到map中.
(9)子键通过“;”和“,”分隔,如:a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z00AKY2NQDwBE/LCAAAOEAACvyAI,aO44gA==; profile-level-id=4D0029\r\n
(10)子键的key和value通过"="分隔。
11.GetSPSPPSFromDescribeSDP()从 SDP中获取 SPS,PPS 信息
(1)并没有从解析的类中得到,而是直接从SDP中得到。通过“sprop-parameter-sets=”及“;”得到。
(2)通过解压函数从得到解压过后的sps和pps.共33个字符:
{0x0, 0x0, 0x0, 0x1, 0x67, 0x4d, 0x0, 0x29, 0x8d, 0x8d, 0x40, 0x3c, 0x1, 0x13,
0xf2, 0xc2, 0x0, 0x0, 0xe, 0x10, 0x0, 0x2, 0xbf, 0x20, 0x8, 0x0, 0x0, 0x0, 0x1, 0x68, 0xee, 0x38, 0x80}
(3)SPS即Sequence Paramater Set,又称作序列参数集。SPS中保存了一组编码视频序列(Coded video sequence)的全局参数。
(4)PPS即Picturle Parameter Set图像参数集。
12.SDP的信息主要存在CNetRtspServer类中的下列三个变量中
(1) RtspProtect RtspProtectArray[MaxRtspProtectCount];
(2)CABLSipParse sipParseV, sipParseA; //sdp 信息分析
二、ZLMediaKit的过程
1.大的流程
(1)RtspSplitter::onRecvHeader(const char *data, size_t len)
(2)RtspSplitter::onRecvContent(const char *data, size_t len)
(3) onWholeRtspPacket(_parser);
(4) RtspSession::handleReq_ANNOUNCE(const Parser &parser)
2.RtspSplitter::onRecvHeader调用_parser.Parse(data)解析RTSP部分
(1)Parser//rtsp/http/sip解析类Parser的主要变量
std::string _strMethod;
std::string _strUrl;
std::string _strTail;
std::string _strContent;
std::string _strNull;
std::string _params;
mutable StrCaseMap _mapHeaders;
mutable StrCaseMap _mapUrlArgs;
(2)_parser.Parse(data)赋值的变量
- _strMethod
- _strUrl
- _strTail
- _mapHeaders
- _strContent
各部分的具体值不再详述。
3.onWholeRtspPacket中对_media_info赋值
(1)是对“rtsp://192.168.169.131:554/live/Camera_00001”解析
(2)MediaInfo的变量
std::string _full_url;//rtsp://192.168.169.131:554/live/Camera_00001
std::string _schema;//rtsp
std::string _host;//192.168.169.131
uint16_t _port = 0;//554
std::string _vhost;//虚拟主机(采用默认的__defaultVhost__)
std::string _app;//live
std::string _streamid;//Camera_00001
std::string _param_strs;//?以后的解析
(3) _media_info._schema = RTSP_SCHEMA;这个代码似乎没有用。
4.RtspSession::handleReq_ANNOUNCE函数的大致过程
(1)定义了lambda函数auto onRes和Broadcast::PublishAuthInvoker invoker 。
(2)真正开始执行NoticeCenter::Instance().emitEvent代码。
(3)先执行invoker 再执行onRes。
5.SdpParser类
解析SDP的类。
(1)void SdpParser::load(const string &sdp)函数是主要的函数。
(2)m=video 0 RTP/AVP 96 的解析
if (4 == sscanf(opt_val.data(), " %15[^ ] %d %15[^ ] %d", type, &port, rtp, &pt))
- []在正则表达式代表范围
- ^在正则表达式代表非
(3)执行完load函数后, std::vector<SdpTrack::Ptr> _track_vec有三个track
- 会话track
- 视频track
- 音频track
(4)SDP的字符:
v=0
o=- 0 0 IN IP4 0.0.0.0
s=Streamed by ZLMediaKit(git hash:099845b,branch:master,build time:Nov 19 2022 12:35:24)
c=IN IP4 0.0.0.0
t=0 0
a=range:npt=now-
a=control:*
m=video 0 RTP/AVP 96
b=AS:2457
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1; profile-level-id=4D0029; sprop-parameter-sets=Z00AKY2NQDwBE/LCAAAOEAACvyAI,aO44gA==
a=control:trackID=0
m=audio 0 RTP/AVP 98
a=rtpmap:98 mpeg4-generic/8000/1
a=fmtp:98 streamtype=5;
(5)Times(必选)-- _track_vec[0]
t=0 0
格式:t=<start time> <stop time>
描述:t字段描述了会话的开始时间和结束时间,<start time> <stop time>为NTP时间,单位是秒;如果<stop time>为0表示过了<start time>之后,会话一直持续;当<start time> 和<stop time>都为0的时候,表示持久会话;建议两个值不设为0,如果设为0,不知道开始时间和结束时间,增大了调度的难度.
(6)v=0-- _track_vec[0]
// v = 0 “v =”字段给出SDP的版本,默认为0
(7)o = (所有者/创建者和会话标识符)
o=- 0 0 IN IP4 0.0.0.0
// o = <username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
// <username> 是用户在始发主机上的登录名
// <sess-id> <sess-id>是一个数字字符串,使得<username><sess-id>nettype><addrtype>和<unicast-address>的元组形成会话的全局唯一标识符。
// <sess-version>是此会话描述的版本号
// <nettype>是一个给出网络类型的文本字符串。 “IN”被定义为具有“Internet”的含义
// <addrtype>是一个文本字符串,给出了后面的地址类型 定义了“IP4”和“IP6”
// <unicast-address>是创建会话的计算机的地址。
o=mozilla...THIS_IS_SDPARTA-67.0.4 7082731800936198286 0 IN IP4 0.0.0.0
(8) s = <会话名称>
s =”字段是文本会话名称。每个会话描述必须有一个且只有一个“s =”字段。“s =”字段不能为空
s=Streamed by ZLMediaKit(git hash:099845b,branch:master,build time:Nov 19 2022 12:35:24)
(9)c=IN IP4 0.0.0.0
// c=<nettype> <addrtype> <connection-address>
// 会话描述必须包含 每个媒体描述中的至少一个“c =”字段或会话级别的单个“c =”字段
// <nettype> “IN”被定义为具有“Internet”的含义
(10)a=range:npt=now-
//用来表示媒体流的长度
a=range:npt=0-72.080000
int ret = sscanf(it->second.data(), "%15[^=]=%15[^-]-%15s", name, start, end); if (3 == ret || 2 == ret) { if (strcmp(start, "now") == 0) { strcpy(start, "0"); } track._start = (float) atof(start); track._end = (float) atof(end); track._duration = track._end - track._start; }
(11)a=(*)
格式 :a=<*>
描述:表示一个会话级别或媒体级别下的0个或多个属性
(12)m=video 0 RTP/AVP 96
m=audio 0 RTP/AVP 98
media information(必选)
格式:m=<media> <port> <transport type> <fmt list>
描述:
<media>表示媒体类型
有"audio","video","application","data"(不向用户显示的数据),"control"(描述额外的控制通道);
<port>表示媒体流发往传输层的端口,对于RTP,偶数端口用来传输数据,奇数端口用来RTSP和RTCP;
<transport>表示传输协议,与"c="一行相关联,一般用RTP/AVP表示,即 Realtime Transport Protocol using the Audio/Video profile over udp,即我们常说的RTP over udp;
<fmt list>表示媒体格式,分为静态绑定和动态绑定
静态绑定:媒体编码方式与RTP负载类型有确定的一一对应关系,如: m=audio 0 RTP/AVP 8
动态绑定:媒体编码方式没有完全确定,需要使用rtpmap进行进一步的说明: 如:m=video 0 RTP/AVP 96
(13)a=rtpmap:96 H264/90000
a=rtpmap:98 mpeg4-generic/8000/1
rtpmap(可选)
格式:a=rtpmap:<payload typee> <encoding name>/<clock rate>/<channel>
描述:
payload type表示动态负载类型,如 98表示h264
encoding name表示编码名称,如H.264
clock rate表示时钟频率,如90000
channel表示通过号
for (it = track._attr.find("rtpmap"); it != track._attr.end() && it->first == "rtpmap";) { auto &rtpmap = it->second; int pt, samplerate, channel; char codec[16] = {0}; sscanf(rtpmap.data(), "%d", &pt); if (track._pt != pt && track._pt != 0xff) { //pt不匹配 it = track._attr.erase(it); continue; } if (4 == sscanf(rtpmap.data(), "%d %15[^/]/%d/%d", &pt, codec, &samplerate, &channel)) { track._codec = codec; track._samplerate = samplerate; track._channel = channel; } else if (3 == sscanf(rtpmap.data(), "%d %15[^/]/%d", &pt, codec, &samplerate)) { track._pt = pt; track._codec = codec; track._samplerate = samplerate; } if (!track._samplerate && track._type == TrackVideo) { //未设置视频采样率时,赋值为90000 track._samplerate = 90000; } ++it; }
(14)a=control:*
a=control:trackID=0
a=control:trackID=1
通过媒体流0、1来发送音频或视频
(15)a=fmtp:96 packetization-mode=1; profile-level-id=4D0029; sprop-parameter-sets=Z00AKY2NQDwBE/LCAAAOEAACvyAI,aO44gA==
a=fmtp:98 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1588
for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp"; ) { auto &fmtp = it->second; int pt; sscanf(fmtp.data(), "%d", &pt);// 如96 if (track._pt != pt && track._pt != 0xff) { //pt不匹配 it = track._attr.erase(it); continue; } track._fmtp = FindField(fmtp.data(), " ", nullptr);//如96后面的字符 +
a=fmtp:<payload type> <format specific parameters>
fmtp,格式参数,即 format parameters;
<payload type>:负载类型,同样对应 RTP 包中的音视频数据负载类型;
< format specific parameters>:指具体参数,或者说对音视频编码信息的一次处理。该信息从编码器得到,比如视频的SPS\PPS等,用于解码端的播放器初始化。
解释:这里面包含一些RTP封包模式,视频质量等级,视频的SPS、PPS等信息。
表示该路会话的的audio是通过RTP来格式传送的,其payload值为97。
当 packetization-mode 的值为 1 时,RTP打包H.264的NALU单元必须使用非交错(non-interleaved)封包模式.
当 profile-level-id的值为 42C01E 时, 第一个字节0x42表示 H.264 的 profile_idc类型Baseline profile , 第二字节代表profile_iop,各个Bit代表视频序列遵循的条款,第三个字节表示 H.264 的 Profile 级别,0x1E即30代表了levle_idc为3,即30/3,具体信息参考H.264的SPS PPS即可。
另一个解释这3个字节都是16进制表示。第一个字节表示profile_idc(0x42, 0x4d, 0x64),第二个字节表示profile-iop,第三个字节表示level。其中第一个字节和第三个字节比较好理解,
sprop-parameter-sets是SPS和PPS的的Base64之后的字符串,中间以逗号分割。
(16)b=AS:2457
b=AS:32
Bandwidth(可选)
格式: b=<modifier>:<bandwidth-value>
描述:该选项描述了建议的带宽,单位 kbs/s,可选,modifier包括两种类型,CT和AS,CT表示总带宽,AS表示单个媒体带宽的最大值;bandwidth-value表示具体的带宽。