既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
if ((prop = req->ensure\_property\_string("streamurl")) == NULL) {
return srs\_error\_wrap(err, "not streamurl");
}
string streamurl = prop->to\_str();
string clientip;
if ((prop = req->ensure\_property\_string("clientip")) != NULL) {
clientip = prop->to\_str();
}
if (clientip.empty()){
clientip = dynamic_cast<SrsHttpMessage\*>(r)->connection()->remote\_ip();
// Overwrite by ip from proxy.
string oip = srs\_get\_original\_ip(r);
if (!oip.empty()) {
clientip = oip;
}
}
string api;
if ((prop = req->ensure\_property\_string("api")) != NULL) {
api = prop->to\_str();
}
string tid;
if ((prop = req->ensure\_property\_string("tid")) != NULL) {
tid = prop->to\_str();
}
// The RTC user config object.
SrsRtcUserConfig ruc;
ruc.req_->ip = clientip;
ruc.api_ = api;
// 解析RTMP URL
srs\_parse\_rtmp\_url(streamurl, ruc.req_->tcUrl, ruc.req_->stream);
// 解析RTC URL
srs\_discovery\_tc\_url(ruc.req_->tcUrl, ruc.req_->schema, ruc.req_->host, ruc.req_->vhost,
ruc.req_->app, ruc.req_->stream, ruc.req_->port, ruc.req_->param);
// discovery vhost, resolve the vhost from config
SrsConfDirective\* parsed_vhost = _srs_config->get\_vhost(ruc.req_->vhost);
if (parsed_vhost) {
ruc.req_->vhost = parsed_vhost->arg0();
}
if ((err = http\_hooks\_on\_publish(ruc.req_)) != srs_success) {
return srs\_error\_wrap(err, "RTC: http\_hooks\_on\_publish");
}
// For client to specifies the candidate(EIP) of server.
string eip = r->query\_get("eip");
if (eip.empty()) {
eip = r->query\_get("candidate");
}
string codec = r->query\_get("codec");
srs\_trace("RTC publish %s, api=%s, tid=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, codec=%s",
streamurl.c\_str(), api.c\_str(), tid.c\_str(), clientip.c\_str(), ruc.req_->app.c\_str(), ruc.req_->stream.c\_str(),
remote_sdp_str.length(), eip.c\_str(), codec.c\_str()
);
ruc.eip_ = eip;
ruc.codec_ = codec;
ruc.publish_ = true;
ruc.dtls_ = ruc.srtp_ = true;
// TODO: FIXME: It seems remote\_sdp doesn't represents the full SDP information.
// 解析sdp
if ((err = ruc.remote_sdp_.parse(remote_sdp_str)) != srs_success) {
return srs\_error\_wrap(err, "parse sdp failed: %s", remote_sdp_str.c\_str());
}
// 校验SDP
if ((err = check\_remote\_sdp(ruc.remote_sdp_)) != srs_success) {
return srs\_error\_wrap(err, "remote sdp check failed");
}
SrsSdp local_sdp;
// TODO: FIXME: move to create\_session.
// Config for SDP and session.
local_sdp.session_config_.dtls_role = _srs_config->get\_rtc\_dtls\_role(ruc.req_->vhost);
local_sdp.session_config_.dtls_version = _srs_config->get\_rtc\_dtls\_version(ruc.req_->vhost);
// Whether enabled.
bool server_enabled = _srs_config->get\_rtc\_server\_enabled();
bool rtc_enabled = _srs_config->get\_rtc\_enabled(ruc.req_->vhost);
if (server_enabled && !rtc_enabled) {
srs\_warn("RTC disabled in vhost %s", ruc.req_->vhost.c\_str());
}
if (!server_enabled || !rtc_enabled) {
return srs\_error\_new(ERROR_RTC_DISABLED, "Disabled server=%d, rtc=%d, vhost=%s",
server_enabled, rtc_enabled, ruc.req_->vhost.c\_str());
}
// TODO: FIXME: When server enabled, but vhost disabled, should report error.
// 创建RTC会话,并写入answer sdp
SrsRtcConnection\* session = NULL;
if ((err = server_->create\_session(&ruc, local_sdp, &session)) != srs_success) {
return srs\_error\_wrap(err, "create session");
}
//将answer sdp写入到ostringstream
ostringstream os;
if ((err = local_sdp.encode(os)) != srs_success) {
return srs\_error\_wrap(err, "encode sdp");
}
string local_sdp_str = os.str();
// Filter the \r\n to \\r\\n for JSON.
string local_sdp_escaped = srs\_string\_replace(local_sdp_str.c\_str(), "\r\n", "\\r\\n");
// 设置响应参数
res->set("code", SrsJsonAny::integer(ERROR_SUCCESS));
res->set("server", SrsJsonAny::str(SrsStatistic::instance()->server\_id().c\_str()));
// TODO: add candidates in response json?
res->set("sdp", SrsJsonAny::str(local_sdp_str.c\_str()));
res->set("sessionid", SrsJsonAny::str(session->username().c\_str()));
srs\_trace("RTC username=%s, offer=%dB, answer=%dB", session->username().c\_str(),
remote_sdp_str.length(), local_sdp_escaped.length());
srs\_trace("RTC remote offer: %s", srs\_string\_replace(remote_sdp_str.c\_str(), "\r\n", "\\r\\n").c\_str());
srs\_trace("RTC local answer: %s", local_sdp_escaped.c\_str());
return err;
}
### 解析sdp
## 推流解析SDP
然后 `ruc.remote_sdp_.parse(remote_sdp_str)`解析SDP,`SrsSdp::parse`首先对读取每一行然后对行调用`parse_line`进行解析
srs_error_t SrsSdp::parse(const std::string& sdp_str)
{
srs_error_t err = srs_success;
// All webrtc SrsSdp annotated example
// @see: https://tools.ietf.org/html/draft-ietf-rtcweb-SrsSdp-11
// Sdp example
// session info
// v=
// o=
// s=
// t=
// media description
// m=
// a=
// ...
// media description
// m=
// a=
// ...
std::istringstream is(sdp_str);
std::string line;
// 以行读取
while (getline(is, line)) {
srs\_verbose("%s", line.c\_str());
if (line.size() < 2 || line[1] != '=') {
return srs\_error\_new(ERROR_RTC_SDP_DECODE, "invalid sdp line=%s", line.c\_str());
}
if (!line.empty() && line[line.size()-1] == '\r') {
line.erase(line.size()-1, 1);
}
// Strip the space of line, for pion WebRTC client.
line = srs\_string\_trim\_end(line, " ");
// 解析行
if ((err = parse\_line(line)) != srs_success) {
return srs\_error\_wrap(err, "parse sdp line failed");
}
}
// The msid/tracker/mslabel is optional for SSRC, so we copy it when it's empty.
for (std::vector<SrsMediaDesc>::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {
SrsMediaDesc& media_desc = \*iter;
for (size_t i = 0; i < media_desc.ssrc_infos_.size(); ++i) {
SrsSSRCInfo& ssrc_info = media_desc.ssrc_infos_.at(i);
if (ssrc_info.msid_.empty()) {
ssrc_info.msid_ = media_desc.msid_;
}
if (ssrc_info.msid_tracker_.empty()) {
ssrc_info.msid_tracker_ = media_desc.msid_tracker_;
}
if (ssrc_info.mslabel_.empty()) {
ssrc_info.mslabel_ = media_desc.msid_;
}
if (ssrc_info.label_.empty()) {
ssrc_info.label_ = media_desc.msid_tracker_;
}
}
}
return err;
}
#### parse\_line
srs_error_t SrsSdp::parse_line(const std::string& line)
{
srs_error_t err = srs_success;
std::string content = line.substr(2);
switch (line[0]) {
case 'o': {
return parse\_origin(content);
}
case 'v': {
return parse\_version(content);
}
case 's': {
return parse\_session\_name(content);
}
case 't': {
return parse\_timing(content);
}
case 'a': {
if (in_media_session_) {
return media_descs_.back().parse\_line(line);
}
return parse\_attribute(content);
}
case 'm': {
return parse\_media\_description(content);
}
case 'c': {
// TODO: process c-line
break;
}
default: {
srs\_trace("ignore sdp line=%s", line.c\_str());
break;
}
}
return err;
}
### 校验sdp
`check_remote_sdp`对SDP进行校验,主要校验是否支持bound媒体复用和媒体能力
srs_error_t SrsGoApiRtcPublish::check_remote_sdp(const SrsSdp& remote_sdp)
{
srs_error_t err = srs_success;
if (remote_sdp.group_policy_ != "BUNDLE") {
return srs\_error\_new(ERROR_RTC_SDP_EXCHANGE, "now only support BUNDLE, group policy=%s", remote_sdp.group_policy_.c\_str());
}
if (remote_sdp.media_descs_.empty()) {
return srs\_error\_new(ERROR_RTC_SDP_EXCHANGE, "no media descriptions");
}
for (std::vector<SrsMediaDesc>::const_iterator iter = remote_sdp.media_descs_.begin(); iter != remote_sdp.media_descs_.end(); ++iter) {
if (iter->type_ != "audio" && iter->type_ != "video") {
return srs\_error\_new(ERROR_RTC_SDP_EXCHANGE, "unsupport media type=%s", iter->type_.c\_str());
}
if (! iter->rtcp_mux_) {
return srs\_error\_new(ERROR_RTC_SDP_EXCHANGE, "now only suppor rtcp-mux");
}
if (iter->recvonly_) {
return srs\_error\_new(ERROR_RTC_SDP_EXCHANGE, "publish API only support sendrecv/sendonly");
}
}
return err;
}
## 生成SDP
### 生成本地sdp
`server_->create_session`进行媒体协商并创建会话信息和生成SDP
srs_error_t SrsRtcServer::create_session(SrsRtcUserConfig* ruc, SrsSdp& local_sdp, SrsRtcConnection** psession)
{
…
SrsRtcSource* source = NULL;
// publish创建SrsRtcSource,即创建流,play绑定SrsRtcSource,即绑定流
if ((err = _srs_rtc_sources->fetch_or_create(req, &source)) != srs_success) {
return srs_error_wrap(err, “create source”);
}
…
// TODO: FIXME: add do_create_session to error process.
SrsRtcConnection* session = new SrsRtcConnection(this, cid);
// 创建RTC会话,设置answer sdp
if ((err = do_create_session(ruc, local_sdp, session)) != srs_success) {
srs_freep(session);
return srs_error_wrap(err, “create session”);
}
\*psession = session;
return err;
}
### 将sdp编码成字符串
`local_sdp.encode(os)`将SDP编码成响应的sdp字符串
![img](https://img-blog.csdnimg.cn/img_convert/586a055d9e9daaa671800a46c0156942.png)
![img](https://img-blog.csdnimg.cn/img_convert/e345e460e68e342f8e7a48de288a9f44.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
code(os)`将SDP编码成响应的sdp字符串
[外链图片转存中...(img-LfUhf24X-1715639778859)]
[外链图片转存中...(img-RG5Z26Sx-1715639778859)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**