ZLMedia中RTSP协议的处理简要分析(1)
1.RTSP协议的处理在 RtspSplitter::onRecvHeader中进行。
2.RTSP协议中SDP的处理在RtspSplitter::onRecvContent中进行。
3.收到的RTSP数据在Parser类中Parse函数解析后存到和相关的成员变量中,各个key和value的值通过函数FindField得到。主要是一个multimap类型的变量中。
void Parser::Parse(const char *buf) { //解析 const char *start = buf; Clear(); while (true) { auto line = FindField(start, NULL, "\r\n"); if (line.size() == 0) { break; } if (start == buf) { _strMethod = FindField(line.data(), NnULL, " "); auto strFullUrl = FindField(line.data(), " ", " "); auto args_pos = strFullUrl.find('?'); if (args_pos != string::npos) { _strUrl = strFullUrl.substr(0, args_pos); _params = strFullUrl.substr(args_pos + 1); _mapUrlArgs = parseArgs(_params); } else { _strUrl = strFullUrl; } _strTail = FindField(line.data(), (strFullUrl + " ").data(), NULL); } else { auto field = FindField(line.data(), NULL, ": "); auto value = FindField(line.data(), ": ", NULL); if (field.size() != 0) { _mapHeaders.emplace_force(field, value); } } start = start + line.size() + 2; if (strncmp(start, "\r\n", 2) == 0) { //协议解析完毕 _strContent = FindField(start, "\r\n", NULL); break; } } }
string FindField(const char* buf, const char* start, const char *end ,size_t bufSize) { if(bufSize <=0 ){ bufSize = strlen(buf); } const char *msg_start = buf, *msg_end = buf + bufSize; size_t len = 0; if (start != NULL) { len = strlen(start); msg_start = strstr(buf, start); } if (msg_start == NULL) { return ""; } msg_start += len; if (end != NULL) { msg_end = strstr(msg_start, end); if (msg_end == NULL) { return ""; } } return string(msg_start, msg_end);//具体怎么用还有点不理解 }
4.RSP的回复代码:
bool RtspSession::sendRtspResponse(const string &res_code, const StrCaseMap &header_const, const string &sdp, const char *protocol){ auto header = header_const; header.emplace("CSeq",StrPrinter << _cseq); if(!_sessionid.empty()){ header.emplace("Session", _sessionid); } header.emplace("Server",kServerName); header.emplace("Date",dateStr()); if(!sdp.empty()){ header.emplace("Content-Length",StrPrinter << sdp.size()); header.emplace("Content-Type","application/sdp"); } _StrPrinter printer; printer << protocol << " " << res_code << "\r\n"; for (auto &pr : header){ printer << pr.first << ": " << pr.second << "\r\n"; } printer << "\r\n"; if(!sdp.empty()){ printer << sdp; } // DebugP(this) << printer; return send(std::make_shared<BufferString>(std::move(printer))) > 0 ; } ssize_t RtspSession::send(Buffer::Ptr pkt){ // if(!_enableSendRtp){ // DebugP(this) << pkt->data(); // } _bytes_usage += pkt->size(); return TcpSession::send(std::move(pkt)); }
(1)代码中_cseq为全局变量,在void RtspSession::onWholeRtspPacket(Parser &parser)void RtspSession::onWholeRtspPacket(Parser &parser)中 _cseq = atoi(parser["CSeq"].data());得到。
(2)_sessionid在此还为空。
(3)kServerName:在\ZLMediaKit\src\Common\macros.cpp中
//请遵循MIT协议,勿修改服务器声明
#if !defined(ENABLE_VERSION)
const char kServerName[] = "ZLMediaKit-6.0(build in " __DATE__ " " __TIME__ ")";
#else
const char kServerName[] = "ZLMediaKit(git hash:" COMMIT_HASH ",branch:" BRANCH_NAME ",build time:" __DATE__ " " __TIME__ ")";
#endif
为什么这么写,还不清楚。
5.ANNOUNCE的回复
在ssize_t RtspSplitter::onRecvHeader(const char *data, size_t len)和void RtspSplitter::onRecvContent(const char *data, size_t len)中处理。回复的代码统一在void RtspSession::handleReq_ANNOUNCE(const Parser &parser)中处理,代码如下:

void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { auto full_url = parser.FullUrl(); _content_base = full_url; if (end_with(full_url, ".sdp")) { //去除.sdp后缀,防止EasyDarwin推流器强制添加.sdp后缀 full_url = full_url.substr(0, full_url.length() - 4); _media_info.parse(full_url); } if (_media_info._app.empty() || _media_info._streamid.empty()) { //推流rtsp url必须最少两级(rtsp://host/app/stream_id),不允许莫名其妙的推流url static constexpr auto err = "rtsp推流url非法,最少确保两级rtsp url"; sendRtspResponse("403 Forbidden", {"Content-Type", "text/plain"}, err); throw SockException(Err_shutdown, StrPrinter << err << ":" << full_url); } auto onRes = [this, parser, full_url](const string &err, const ProtocolOption &option) { if (!err.empty()) { sendRtspResponse("401 Unauthorized", { "Content-Type", "text/plain" }, err); shutdown(SockException(Err_shutdown, StrPrinter << "401 Unauthorized:" << err)); return; } assert(!_push_src); auto src = MediaSource::find(RTSP_SCHEMA, _media_info._vhost, _media_info._app, _media_info._streamid); auto push_failed = (bool)src; while (src) { //尝试断连后继续推流 auto rtsp_src = dynamic_pointer_cast<RtspMediaSourceImp>(src); if (!rtsp_src) { //源不是rtsp推流产生的 break; } auto ownership = rtsp_src->getOwnership(); if (!ownership) { //获取推流源所有权失败 break; } _push_src = std::move(rtsp_src); _push_src_ownership = std::move(ownership); push_failed = false; break; } if (push_failed) { sendRtspResponse("406 Not Acceptable", { "Content-Type", "text/plain" }, "Already publishing."); string err = StrPrinter << "ANNOUNCE:" << "Already publishing:" << _media_info._vhost << " " << _media_info._app << " " << _media_info._streamid << endl; throw SockException(Err_shutdown, err); } SdpParser sdpParser(parser.Content()); _sessionid = makeRandStr(12); _sdp_track = sdpParser.getAvailableTrack(); if (_sdp_track.empty()) { // sdp无效 static constexpr auto err = "sdp中无有效track"; sendRtspResponse("403 Forbidden", { "Content-Type", "text/plain" }, err); shutdown(SockException(Err_shutdown, StrPrinter << err << ":" << full_url)); return; } _rtcp_context.clear(); for (auto &track : _sdp_track) { _rtcp_context.emplace_back(std::make_shared<RtcpContextForRecv>()); } if (!_push_src) { _push_src = std::make_shared<RtspMediaSourceImp>(_media_info._vhost, _media_info._app, _media_info._streamid); //获取所有权 _push_src_ownership = _push_src->getOwnership(); _push_src->setProtocolOption(option); _push_src->setSdp(parser.Content()); } _push_src->setListener(dynamic_pointer_cast<MediaSourceEvent>(shared_from_this())); _continue_push_ms = option.continue_push_ms; sendRtspResponse("200 OK"); }; weak_ptr<RtspSession> weakSelf = dynamic_pointer_cast<RtspSession>(shared_from_this()); Broadcast::PublishAuthInvoker invoker = [weakSelf, onRes](const string &err, const ProtocolOption &option) { auto strongSelf = weakSelf.lock(); if (!strongSelf) { return; } strongSelf->async([weakSelf, onRes, err, option]() { auto strongSelf = weakSelf.lock(); if (!strongSelf) { return; } onRes(err, option); }); }; //rtsp推流需要鉴权 auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtsp_push, _media_info, invoker, static_cast<SockInfo &>(*this)); if (!flag) { //该事件无人监听,默认不鉴权 onRes("", ProtocolOption()); } }
(1)full_url是推流的全连接。
(2)_media_info在onRecvContent中得到,主要存储连接信息:
{_full_url = "rtsp://192.168.169.197:554/live/Camera_00001", _schema = "rtsp", _host = "192.168.169.197", _port = 554, _vhost = "__defaultVhost__", _app = "live",
_streamid = "Camera_00001", _param_strs = ""}
(3)SdpParser sdpParser(parser.Content());对sdp内容进行解析。通过 sdpParser.getAvailableTrack得到音视频的信息。
(4)_sessionid是一个1从字母和数字的数组中随机取出的12位数,不能保证完全不重复。
(5)用ffmpeg推流rtsp收到的SDP如下:
经过SdpParser解析以后,存到std::vector<SdpTrack::Ptr> _sdp_track中,打印出的各变量为:
p { _sdp_track[0]->getName(), _sdp_track[1]->getName()} {"H264", "MPEG4-GENERIC"}
p { _sdp_track[0]->_pt, _sdp_track[1]->_pt} {96, 97}
p { _sdp_track[0]->_channel, _sdp_track[1]->_channel} {1, 1}
p { _sdp_track[0]->_samplerate, _sdp_track[1]->_samplerate} {90000, 16000}
p { _sdp_track[0]->_type, _sdp_track[1]->_type} {mediakit::TrackVideo, mediakit::TrackAudio}
p { _sdp_track[0]->_codec, _sdp_track[1]->_codec} {"H264", "MPEG4-GENERIC"}
p { _sdp_track[0]->_fmtp, _sdp_track[1]->_fmtp} {"packetization-mode=1; sprop-parameter-sets=Z00AKY2NQDwBE/LCAAAOEAACvyAI,aO44gA==; profile-level-id=4D0029", "profile-level-id=1;mode=AAC-
hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=1408"}
p { _sdp_track[0]->_control, _sdp_track[1]->_control} {"streamid=0", "streamid=1"}
p { _sdp_track[0]->_inited, _sdp_track[1]->_inited} {false, false}
p { _sdp_track[0]->_interleaved, _sdp_track[1]->_interleaved} $69 = "\000"
p { _sdp_track[0]->_seq, _sdp_track[1]->_seq} {0, 0}
p { _sdp_track[0]->_ssrc, _sdp_track[1]->_ssrc} {0, 0}
p { _sdp_track[0]->_time_stamp, _sdp_track[1]->_time_stamp}{0, 0}
p { _sdp_track[0]->_t, _sdp_track[1]->_t} {"", ""}
p { _sdp_track[0]->_b, _sdp_track[1]->_b} {"AS:2457", "AS:32"}
p { _sdp_track[0]->_port, _sdp_track[1]->_port} {0, 0}
p { _sdp_track[0]->_duration, _sdp_track[1]->_duration} {0, 0}
p { _sdp_track[0]->_start, _sdp_track[1]->_start} {0, 0}
p { _sdp_track[0]->_end, _sdp_track[1]->_end} {0, 0}
p { _sdp_track[0]->_other, _sdp_track[1]->_other}{std::map with 0 elements, std::map with 0 elements}
p { _sdp_track[0]->_attr, _sdp_track[1]->_attr}{std::multimap with 3 elements = {["control"] = "streamid=0", ["fmtp"] = "96 packetization-mode=1; sprop-parameter-sets=Z00AKY2NQDwBE/LCAAAOEAACvyAI,aO44gA==; profile-level-id=4D0029",
["rtpmap"] = "96 H264/90000"},
std::multimap with 3 elements = {["control"] = "streamid=1", ["fmtp"] = "97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=1408", ["rtpmap"] = "97 MPEG4-GENERIC/16000/1"}}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!